mixdog 1.3.1 → 1.3.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 (468) hide show
  1. package/README.md +8 -4
  2. package/package.json +8 -8
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/scripts/verify-runtime-dependency-versions.mjs +158 -0
  5. package/src/agents/reviewer/AGENT.md +5 -7
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +9 -1
  8. package/src/defaults/skills/browser-use/SKILL.md +6 -3
  9. package/src/defaults/skills/local-provider/SKILL.md +33 -6
  10. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  11. package/src/defaults/skills/setup/SKILL.md +23 -1
  12. package/src/defaults/skills/setup/references/actions.md +50 -3
  13. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  14. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  15. package/src/defaults/skills/visualize/SKILL.md +9 -6
  16. package/src/defaults/skills/visualize/references/craft.md +8 -1
  17. package/src/help.mjs +1 -1
  18. package/src/lib/keychain-cjs.cjs +4 -1
  19. package/src/lib/rules-builder.cjs +14 -7
  20. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  21. package/src/rules/agent-mode/MIXY.md +6 -8
  22. package/src/rules/lead/LEAD.md +7 -10
  23. package/src/rules/shared/10-tool-workflow.md +4 -0
  24. package/src/rules/shared/20-research.md +2 -0
  25. package/src/rules/shared/45-destructive.md +5 -1
  26. package/src/rules/shared/60-verification.md +6 -4
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  30. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  31. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  32. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  33. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  35. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  37. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  38. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  39. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  40. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  41. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
  45. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  46. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
  48. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  49. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
  50. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  51. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  52. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  54. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  55. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
  56. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  57. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  59. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  60. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  62. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  64. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  65. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  69. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  70. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  72. package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
  73. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
  74. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  75. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
  78. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  79. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  80. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
  81. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  82. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  83. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
  85. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
  86. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  87. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
  90. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  92. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  93. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  94. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
  95. package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
  96. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  97. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  98. package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
  99. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
  100. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
  102. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
  103. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
  107. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
  108. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  109. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  110. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
  111. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
  112. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  113. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
  114. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  115. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  116. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  117. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  118. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
  119. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
  120. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
  123. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
  124. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  125. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  126. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
  127. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  128. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
  129. package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
  130. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
  131. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  132. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  134. package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
  135. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
  136. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
  137. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  138. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
  139. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
  140. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  143. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
  144. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
  145. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  146. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
  147. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  148. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  149. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  150. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
  151. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
  155. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  156. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
  157. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
  158. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  160. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  161. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
  163. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
  166. package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
  167. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
  168. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  169. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  170. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  171. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  172. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  173. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
  174. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
  178. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
  179. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  180. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  181. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
  182. package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
  183. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
  184. package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
  185. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  186. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  189. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  190. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
  191. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  196. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
  199. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
  200. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  201. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  202. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  203. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  204. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  205. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
  206. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  207. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  208. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  209. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  210. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  211. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  212. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  213. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  214. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  215. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  216. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  217. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  218. package/src/runtime/attachments/legacy-office.mjs +110 -0
  219. package/src/runtime/attachments/limits.mjs +5 -1
  220. package/src/runtime/attachments/office-extract.mjs +3 -6
  221. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  222. package/src/runtime/attachments/prompt-restore.mjs +70 -0
  223. package/src/runtime/attachments/store.mjs +109 -33
  224. package/src/runtime/browser-bridge/client.mjs +73 -20
  225. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  226. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  227. package/src/runtime/channels/lib/state-file.mjs +15 -15
  228. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  229. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  230. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  231. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  232. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  233. package/src/runtime/effort-judge/judge-client.mjs +66 -16
  234. package/src/runtime/effort-judge/model-install.mjs +47 -7
  235. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  236. package/src/runtime/local-provider/catalog.mjs +5 -2
  237. package/src/runtime/local-provider/registered-models.mjs +64 -27
  238. package/src/runtime/local-provider/server.mjs +2 -3
  239. package/src/runtime/media/adapters/image-api.mjs +89 -0
  240. package/src/runtime/media/auth.mjs +64 -1
  241. package/src/runtime/media/catalog.mjs +66 -4
  242. package/src/runtime/media/jobs.mjs +4 -0
  243. package/src/runtime/media/lanes.mjs +46 -5
  244. package/src/runtime/media/media-usage.mjs +17 -0
  245. package/src/runtime/media/renditions.mjs +15 -15
  246. package/src/runtime/media/store.mjs +36 -35
  247. package/src/runtime/media/tool.mjs +8 -1
  248. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  249. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  250. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  251. package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
  252. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  253. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  254. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  255. package/src/runtime/office/core/journal.mjs +7 -14
  256. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  257. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  258. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  259. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  260. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  261. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  262. package/src/runtime/office/portable/portable-soffice.mjs +135 -29
  263. package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
  264. package/src/runtime/shared/agent-avatar.mjs +3 -2
  265. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  266. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  267. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  268. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  269. package/src/runtime/shared/agent-room-host.d.mts +31 -0
  270. package/src/runtime/shared/agent-room-host.mjs +58 -0
  271. package/src/runtime/shared/agent-store.mjs +24 -7
  272. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  273. package/src/runtime/shared/atomic-file.d.mts +50 -0
  274. package/src/runtime/shared/atomic-file.mjs +394 -132
  275. package/src/runtime/shared/background-tasks.mjs +7 -4
  276. package/src/runtime/shared/bot-hops.mjs +35 -0
  277. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  278. package/src/runtime/shared/bounded-download.mjs +4 -2
  279. package/src/runtime/shared/config.mjs +39 -9
  280. package/src/runtime/shared/defaults-separation.mjs +6 -2
  281. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  282. package/src/runtime/shared/err-text.mjs +4 -0
  283. package/src/runtime/shared/error-presentation.mjs +22 -4
  284. package/src/runtime/shared/file-has-data.mjs +28 -0
  285. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  286. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  287. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  288. package/src/runtime/shared/projects.mjs +50 -41
  289. package/src/runtime/shared/provider-accounts.mjs +17 -12
  290. package/src/runtime/shared/redact-directories.mjs +18 -0
  291. package/src/runtime/shared/remove-directory.mjs +28 -0
  292. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  293. package/src/runtime/shared/routine-trigger.mjs +14 -6
  294. package/src/runtime/shared/run-child.d.mts +3 -0
  295. package/src/runtime/shared/run-child.mjs +24 -3
  296. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  297. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  298. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  299. package/src/runtime/shared/singleton-owner.mjs +7 -6
  300. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  301. package/src/runtime/shared/staged-update.mjs +4 -2
  302. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  303. package/src/runtime/shared/tool-execution-contract.mjs +10 -0
  304. package/src/runtime/shared/tool-work-units.mjs +12 -9
  305. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  306. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  307. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  308. package/src/runtime/shared/turn-snapshot.mjs +277 -124
  309. package/src/runtime/shared/user-cwd.mjs +9 -4
  310. package/src/runtime/shared/user-data-guard.mjs +92 -61
  311. package/src/runtime/shared/utf8-cut.mjs +10 -0
  312. package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
  313. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  314. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  315. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  316. package/src/runtime/shared/worktrees/registry.mjs +29 -27
  317. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  318. package/src/runtime/tidy/core-install.mjs +26 -4
  319. package/src/runtime/tidy/install.mjs +91 -23
  320. package/src/runtime/tidy/process.mjs +41 -8
  321. package/src/runtime/tidy/resolve.mjs +10 -7
  322. package/src/runtime/web-search/lib/config.mjs +4 -16
  323. package/src/session-runtime/agent-home-delivery.mjs +542 -204
  324. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  325. package/src/session-runtime/agent-room-delivery.mjs +81 -35
  326. package/src/session-runtime/agent-task-sessions.mjs +811 -179
  327. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  328. package/src/session-runtime/boot/apis.mjs +48 -24
  329. package/src/session-runtime/boot/config.mjs +1 -0
  330. package/src/session-runtime/boot/facade.mjs +16 -7
  331. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  332. package/src/session-runtime/boot/providers.mjs +2 -2
  333. package/src/session-runtime/boot/shared.mjs +12 -6
  334. package/src/session-runtime/boot/tools.mjs +23 -3
  335. package/src/session-runtime/bot-status.mjs +432 -0
  336. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  337. package/src/session-runtime/config-lifecycle.mjs +60 -55
  338. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  339. package/src/session-runtime/held-requests.mjs +31 -33
  340. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  341. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  342. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
  343. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  344. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  345. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  346. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  347. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  348. package/src/session-runtime/media-api.mjs +5 -2
  349. package/src/session-runtime/model-recency.mjs +1 -0
  350. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  351. package/src/session-runtime/model-route/set-route.mjs +61 -1
  352. package/src/session-runtime/owned-automation-run.mjs +8 -1
  353. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  354. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  355. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  356. package/src/session-runtime/route-resolution.mjs +16 -8
  357. package/src/session-runtime/route-state.mjs +1 -59
  358. package/src/session-runtime/routine-home-turn.mjs +5 -3
  359. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  360. package/src/session-runtime/runtime-paths.mjs +11 -5
  361. package/src/session-runtime/runtime-review-api.mjs +4 -1
  362. package/src/session-runtime/runtime-tunables.mjs +6 -0
  363. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  364. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  365. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  366. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  367. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
  368. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  369. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  370. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
  371. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  372. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  373. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  374. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  375. package/src/session-runtime/services/agent-tool.mjs +6 -0
  376. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  377. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  378. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  379. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  380. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  381. package/src/session-runtime/services/provider-admin.mjs +26 -6
  382. package/src/session-runtime/services/schedule-access.mjs +33 -4
  383. package/src/session-runtime/services/seeds.mjs +4 -3
  384. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  385. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  386. package/src/session-runtime/services/worktree-access.mjs +27 -0
  387. package/src/session-runtime/session-create/materialize.mjs +2 -3
  388. package/src/session-runtime/session-create/session-options.mjs +15 -2
  389. package/src/session-runtime/session-hooks.mjs +9 -17
  390. package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
  391. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  392. package/src/session-runtime/setup-tool/executor.mjs +12 -2
  393. package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
  394. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  395. package/src/session-runtime/skills-api.mjs +12 -5
  396. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
  397. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  398. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  399. package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
  400. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  401. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  402. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  403. package/src/session-runtime/tool-policy-refresh.mjs +48 -369
  404. package/src/session-runtime/tool-surface.mjs +10 -4
  405. package/src/session-runtime/turn/session-ops.mjs +8 -0
  406. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  407. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  408. package/src/session-runtime/turn/turn-run.mjs +7 -2
  409. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  410. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
  411. package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
  412. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  413. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  414. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  415. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  416. package/src/session-runtime/workspace/transitions.mjs +279 -67
  417. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  418. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  419. package/src/standalone/daemon-agent-control.mjs +4 -1
  420. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  421. package/src/standalone/daemon-session-control.mjs +173 -15
  422. package/src/standalone/daemon-telemetry.mjs +9 -0
  423. package/src/standalone/daemon.mjs +33 -10
  424. package/src/standalone/local-session-runtime.mjs +33 -16
  425. package/src/standalone/session-client.mjs +7 -2
  426. package/src/standalone/session-protocol.mjs +8 -0
  427. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  428. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  429. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  430. package/src/standalone/session-service.mjs +21 -0
  431. package/src/standalone/session-state-projection.mjs +19 -9
  432. package/src/standalone/session-tool-output.mjs +36 -9
  433. package/src/standalone/staged-import.mjs +47 -0
  434. package/src/tui/App.jsx +2 -0
  435. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  436. package/src/tui/app/message-selector.mjs +15 -2
  437. package/src/tui/app/model-options.mjs +16 -2
  438. package/src/tui/app/model-picker.mjs +2 -1
  439. package/src/tui/app/route-pickers.mjs +3 -1
  440. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  441. package/src/tui/app/slash-commands.mjs +1 -1
  442. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  443. package/src/tui/dist/index.mjs +617 -369
  444. package/src/tui/paste-attachments.mjs +0 -1
  445. package/src/tui/paste-text-policy.mjs +4 -5
  446. package/src/tui/prompt-history-store.mjs +16 -21
  447. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  448. package/src/tui/session/context-state.mjs +4 -0
  449. package/src/tui/session/notice-surface.mjs +3 -0
  450. package/src/tui/session/session-api/intake/abort.mjs +52 -18
  451. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  452. package/src/tui/session/session-api/media.mjs +2 -1
  453. package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
  454. package/src/tui/session/session-api/routes-write.mjs +203 -0
  455. package/src/tui/session/session-api/routes.mjs +23 -247
  456. package/src/tui/session/session-api/settings.mjs +14 -1
  457. package/src/tui/session/session-flow/drain.mjs +28 -3
  458. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  459. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  460. package/src/tui/session/session-flow/steering.mjs +8 -3
  461. package/src/tui/session/tool-approval.mjs +142 -14
  462. package/src/tui/session/transcript-intake.mjs +2 -0
  463. package/src/tui/session/turn.mjs +23 -14
  464. package/src/tui/transcript-tool-failures.mjs +8 -2
  465. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  466. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  467. package/src/rules/agent-mode/ROUTINE.md +0 -6
  468. package/src/session-runtime/room-member-gate.mjs +0 -48
@@ -1,16 +1,52 @@
1
- // Cross-process advisory lock for patch/write operations. Uses a sibling
2
- // lockfile written exclusively (`wx`) and a process.kill(pid, 0) liveness
3
- // check to clean up after crashed holders. Pair with the in-process
4
- // withPathLock for the same target: in-process serialises async callers
5
- // in this Node, advisory lock serialises across Node processes.
6
- import { openSync, closeSync, writeSync, readFileSync, unlinkSync, mkdirSync } from 'node:fs';
1
+ // Cross-process advisory lock for patch/write operations. Uses a lockfile
2
+ // written exclusively (`wx`) and a process.kill(pid, 0) liveness check to
3
+ // clean up after crashed holders. Pair with the in-process withPathLock for
4
+ // the same target: in-process serialises async callers in this Node, advisory
5
+ // lock serialises across Node processes.
6
+ import { openSync, closeSync, writeSync, readFileSync, unlinkSync, realpathSync } from 'node:fs';
7
7
  import { compareCodePoints } from '../../../../shared/code-point-order.mjs';
8
+ import { ensurePrivateRuntimeRoot } from '../../../../shared/runtime-root.mjs';
8
9
  import { sleep } from '../../../../shared/sleep.mjs';
9
- import { dirname, basename, join } from 'node:path';
10
- import { randomBytes } from 'node:crypto';
10
+ import { homedir } from 'node:os';
11
+ import { dirname, basename, join, resolve } from 'node:path';
12
+ import { createHash, randomBytes } from 'node:crypto';
13
+
14
+ // The deepest existing component (the target itself when present, so a leaf
15
+ // symlink shares its referent's lock like atomicWrite writes through it) is
16
+ // resolved; components not created yet are appended as given.
17
+ function canonicalLockTarget(targetPath) {
18
+ const resolved = resolve(targetPath);
19
+ const tail = [];
20
+ let dir = resolved;
21
+ for (;;) {
22
+ try {
23
+ return join(realpathSync.native(dir), ...tail.reverse());
24
+ } catch {
25
+ if (dirname(dir) === dir) return resolved;
26
+ tail.push(basename(dir));
27
+ dir = dirname(dir);
28
+ }
29
+ }
30
+ }
31
+
32
+ // External files can be shared by profiles, so neither MIXDOG_HOME/DATA_DIR
33
+ // nor MIXDOG_RUNTIME_ROOT may choose this namespace. Temp directories can
34
+ // differ between processes too. Keep locks in the OS user's local home,
35
+ // never beside the target: sync drives such as MYBOX refuse sibling dotfiles.
36
+ // The pid liveness check coordinates only processes on this host.
37
+ function lockDirectory() {
38
+ const root =
39
+ process.platform === 'win32'
40
+ ? join(process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'), 'mixdog')
41
+ : join(homedir(), '.mixdog');
42
+ return join(root, 'locks');
43
+ }
11
44
 
12
45
  function lockFileFor(targetPath) {
13
- return join(dirname(targetPath), `.${basename(targetPath)}.mixdog-lock`);
46
+ const canonical = canonicalLockTarget(targetPath);
47
+ const key = process.platform === 'win32' ? canonical.toLowerCase() : canonical;
48
+ const hash = createHash('sha256').update(key).digest('hex');
49
+ return join(lockDirectory(), `${hash}.mixdog-lock`);
14
50
  }
15
51
 
16
52
  function isPidAlive(pid) {
@@ -87,15 +123,12 @@ function tryCreateLock(lockFile, token) {
87
123
 
88
124
  async function acquireAdvisoryLock(targetPath, { timeoutMs = 5000, pollMs = 25 } = {}) {
89
125
  const lockFile = lockFileFor(targetPath);
90
- // Ensure parent directory exists before openSync('wx'); the lockfile lives
91
- // next to the target, so writes to new files in missing parent dirs would
92
- // otherwise fail with ENOENT here.
93
- mkdirSync(dirname(lockFile), { recursive: true });
126
+ ensurePrivateRuntimeRoot(dirname(lockFile));
94
127
  const token = `${process.pid}.${randomBytes(8).toString('hex')}`;
95
128
  const deadline = Date.now() + timeoutMs;
96
129
  let stalePostDeadlineRetryUsed = false;
97
130
  const timeoutError = (holderPid) => {
98
- const err = new Error(`advisory lock timeout: ${lockFile} held by pid ${holderPid}`);
131
+ const err = new Error(`advisory lock timeout: ${targetPath} (${lockFile}) held by pid ${holderPid}`);
99
132
  err.code = 'EAGAIN';
100
133
  return err;
101
134
  };
@@ -24,9 +24,24 @@ function expectedTargetSnapshotChanged(currentStat, expected) {
24
24
  return !sameFileVersion(currentStat, expected);
25
25
  }
26
26
 
27
+ // Files the agent writes into a user's project are durable by default: without
28
+ // the flush a crash can publish the renamed file with every byte NUL.
29
+ // MIXDOG_ATOMIC_FSYNC=0|false|no|off opts out.
27
30
  function atomicWriteShouldFsync(value) {
28
31
  if (value === true || value === false) return value;
29
- return /^(1|true|yes|on|sync)$/i.test(String(process.env.MIXDOG_ATOMIC_FSYNC || ''));
32
+ return !/^(0|false|no|off)$/i.test(String(process.env.MIXDOG_ATOMIC_FSYNC || ''));
33
+ }
34
+
35
+ // Filesystems that refuse fsync answer with one of these; durability is
36
+ // best-effort there and must not fail the write.
37
+ const FSYNC_TOLERATED = new Set(['EPERM', 'ENOTSUP', 'EINVAL']);
38
+
39
+ async function syncHandle(fh) {
40
+ try {
41
+ await fh.sync();
42
+ } catch (error) {
43
+ if (!FSYNC_TOLERATED.has(error?.code)) throw error;
44
+ }
30
45
  }
31
46
 
32
47
  // 'wx' preflight creates an empty placeholder at targetPath; if the write is
@@ -104,7 +119,10 @@ async function writeTempFile(tmp, content, { mode, fsync }) {
104
119
  if (payloadByteLength(content) > STREAMING_THRESHOLD_BYTES) {
105
120
  // Streaming path: avoid buffering the entire payload through
106
121
  // fh.writeFile (which copies into a single Buffer).
107
- const ws = createWriteStream(tmp, { flags: 'wx', mode });
122
+ // Owner-writable while we flush: the fsync reopens the file for writing,
123
+ // which a read-only mode would refuse; the final mode is applied after.
124
+ const streamMode = fsync ? mode | 0o200 : mode;
125
+ const ws = createWriteStream(tmp, { flags: 'wx', mode: streamMode });
108
126
  // Stream construction precedes open; only the open event proves ownership.
109
127
  ws.once('open', () => {
110
128
  ownsTemp = true;
@@ -115,15 +133,16 @@ async function writeTempFile(tmp, content, { mode, fsync }) {
115
133
  await pipeline(source, ws);
116
134
  if (fsync) {
117
135
  fh = await fsPromises.open(tmp, 'r+');
118
- await fh.sync();
136
+ await syncHandle(fh);
119
137
  await fh.close();
120
138
  fh = null;
139
+ if (streamMode !== mode) await fsPromises.chmod(tmp, mode);
121
140
  }
122
141
  } else {
123
142
  fh = await fsPromises.open(tmp, 'wx', mode);
124
143
  ownsTemp = true;
125
144
  await fh.writeFile(content);
126
- if (fsync) await fh.sync();
145
+ if (fsync) await syncHandle(fh);
127
146
  await fh.close();
128
147
  fh = null;
129
148
  }
@@ -260,7 +279,8 @@ export async function atomicWrite(
260
279
  // beside the real target, so the rename stays on one filesystem.
261
280
  const writeTarget = symlinkWriteTarget(targetPath) ?? targetPath;
262
281
  const dir = dirname(writeTarget);
263
- const tmp = join(dir, `.${basename(writeTarget)}.mixdog-tmp-${randomBytes(4).toString('hex')}`);
282
+ // No leading dot: sync-drive folders (e.g. Naver MYBOX) refuse dot-prefixed names.
283
+ const tmp = join(dir, `${basename(writeTarget)}.mixdog-tmp-${randomBytes(4).toString('hex')}`);
264
284
  const existingStat = statOrNull(writeTarget);
265
285
  let effectiveMode = mode;
266
286
  if (effectiveMode === undefined && existingStat) effectiveMode = existingStat.mode & 0o777;
@@ -3,7 +3,7 @@ import { killShellJob, watchBackgroundShellJob } from '../shell-jobs.mjs';
3
3
  import { renderBackgroundPartialOutput } from '../shell-output.mjs';
4
4
  import { readShellTaskOutput } from '../lib/shell-task-output.mjs';
5
5
  import { normalizeOutputPath } from '../path-utils.mjs';
6
- import { cleanupArtifactOnTaskSettled, consumeTeeArtifact } from './transport-artifacts.mjs';
6
+ import { cleanupArtifactAfter, cleanupArtifactOnTaskSettled, consumeTeeArtifact } from './transport-artifacts.mjs';
7
7
  import { _backgroundResultLines, _prependDestructiveWarning } from './result-format.mjs';
8
8
 
9
9
  const DEFAULT_BACKGROUND_MESSAGE =
@@ -19,6 +19,7 @@ export function shellTaskIdentity(command, options) {
19
19
  callerSessionId: options?.callerSessionId || options?.sessionId || null,
20
20
  routingSessionId: options?.routingSessionId || options?.sessionId || null,
21
21
  clientHostPid: options?.clientHostPid,
22
+ hop: options?.hop,
22
23
  },
23
24
  };
24
25
  }
@@ -61,6 +62,7 @@ export function renderBackgroundedResult({ result, command, cwd, options, starte
61
62
  callerSessionId: options?.callerSessionId || options?.sessionId,
62
63
  routingSessionId: options?.routingSessionId || options?.sessionId,
63
64
  clientHostPid: options?.clientHostPid,
65
+ hop: options?.hop,
64
66
  });
65
67
  } catch {
66
68
  /* best effort */
@@ -68,8 +70,9 @@ export function renderBackgroundedResult({ result, command, cwd, options, starte
68
70
  }
69
71
  // The promoted producer is still writing into the tee file, so it cannot be
70
72
  // consumed here — but it must not survive the command either. Consume (and
71
- // delete) it when the task settles.
72
- if (teePlan) cleanupArtifactOnTaskSettled(teePlan.teePath, result.jobId, consumeTeeArtifact);
73
+ // delete) it when the task settles — for a queued command, its run.
74
+ if (teePlan && result.jobId) cleanupArtifactOnTaskSettled(teePlan.teePath, result.jobId, consumeTeeArtifact);
75
+ else if (teePlan) cleanupArtifactAfter(teePlan.teePath, result.settled, consumeTeeArtifact);
73
76
  let taskBlock = null;
74
77
  if (task) taskBlock = renderBackgroundTask(task, { ownerTool: true });
75
78
  else if (result.jobId) taskBlock = `[task_id: ${result.jobId}]`;
@@ -1,9 +1,9 @@
1
1
  import { renderBackgroundTask, startBackgroundTask } from '../../../../../shared/background-tasks.mjs';
2
2
  import { killShellDescendants, waitForShellDescendants } from '../../lib/shell-descendants.mjs';
3
3
  import { releaseShellJobWorkspace } from '../lib/shell-job-records.mjs';
4
- import { consumeFilterTeeCapture } from '../shell-analysis.mjs';
4
+ import { readFilterTeeCapture } from '../shell-analysis.mjs';
5
5
  import { compactShellOutputLosslessly, renderShellOutputBody } from '../shell-lossless-compact.mjs';
6
- import { removeTransportFileNow } from './transport-artifacts.mjs';
6
+ import { cleanupArtifactAfter, removeTransportFileNow } from './transport-artifacts.mjs';
7
7
  import { DELIVERY_FULL_CHARS, EMPTY_PIECE, fitPieces, shellTextPiece } from '../lib/shell-delivery.mjs';
8
8
  import { shellLogPiece } from '../lib/shell-task-output.mjs';
9
9
  import { shellTaskIdentity } from './backgrounded-result.mjs';
@@ -74,17 +74,16 @@ function survivingDescendantsWarning(result, taskContext) {
74
74
  return [heading, renderBackgroundTask(task, { ownerTool: true })].join('\n');
75
75
  }
76
76
 
77
- // Filter-swallow rescue: the tee file is ALWAYS consumed (deleted) here; its
78
- // tail is attached only when the run failed with an empty visible capture —
79
- // the exact `(no output)` shape that previously cost the model extra
80
- // diagnostic turns.
81
- function filterRescueNote(teePlan, { failed, stdout, stderr }) {
82
- const rescueTail = consumeFilterTeeCapture(teePlan.teePath);
83
- // consumeFilterTeeCapture unlinks best-effort and swallows the failure; on
84
- // Windows a process that survived an unconfirmed kill still holds the file.
85
- // Route it through the same retry + exit-hook path the settlement branch
86
- // uses instead of abandoning it.
87
- removeTransportFileNow(teePlan.teePath);
77
+ // Filter-swallow rescue: the tee file is ALWAYS consumed (deleted) — once the
78
+ // run's tree has settled when its result says it may still run; its tail is
79
+ // attached only when the run failed with an empty visible capture — the exact
80
+ // `(no output)` shape that previously cost the model extra diagnostic turns.
81
+ function filterRescueNote(teePlan, { failed, stdout, stderr }, settled) {
82
+ const rescueTail = readFilterTeeCapture(teePlan.teePath);
83
+ // Removal goes through the retry + exit-hook path: on Windows a process
84
+ // that survived an unconfirmed kill still holds the file.
85
+ if (settled) cleanupArtifactAfter(teePlan.teePath, settled);
86
+ else removeTransportFileNow(teePlan.teePath);
88
87
  if (!failed || !rescueTail || stdout.trim() || stderr.trim()) return '';
89
88
  return `\n\n[filter-swallowed output rescue] the command failed but its trailing filter(s) matched nothing, so the visible output was empty. Unfiltered pipeline output (tail):\n${rescueTail}`;
90
89
  }
@@ -148,7 +147,7 @@ export function renderCompletedResult({
148
147
  const { signal, exitCode, completedExit, benignExit, isReallyErrored } = outcome;
149
148
  const commandExitedNonzero = completedExit && exitCode !== 0 && !benignExit;
150
149
  const rescueNote = teePlan
151
- ? filterRescueNote(teePlan, { failed: isReallyErrored || commandExitedNonzero, stdout, stderr })
150
+ ? filterRescueNote(teePlan, { failed: isReallyErrored || commandExitedNonzero, stdout, stderr }, result.settled)
152
151
  : '';
153
152
  const descendantWarning = survivingDescendantsWarning(result, { command, cwd, options, startedAtMs });
154
153
  // rewriteNote states that the text which RAN is not the text the caller
@@ -79,6 +79,15 @@ export function removeTransportFileNow(file) {
79
79
  finishTransportArtifact(file, removeTransportFile);
80
80
  }
81
81
 
82
+ /** Remove `file` once a run's own lifetime promise settles: a queued command
83
+ * has no job to subscribe to until it runs. */
84
+ export function cleanupArtifactAfter(file, settled, remove = removeTransportFile) {
85
+ if (!file) return;
86
+ registerTransportArtifact(file);
87
+ const done = () => finishTransportArtifact(file, remove);
88
+ Promise.resolve(settled).then(done, done);
89
+ }
90
+
82
91
  export function cleanupArtifactOnTaskSettled(file, jobId, remove = removeTransportFile) {
83
92
  if (!file) return;
84
93
  registerTransportArtifact(file);
@@ -12,7 +12,11 @@ import { recordShellCaptureTelemetry } from './shell-output.mjs';
12
12
  import { normalizeErrorMessage } from './path-diagnostics.mjs';
13
13
  import { invalidateBuiltinResultCache } from './cache-layers.mjs';
14
14
  import { formatShellToolFailure } from './bash-tool/result-format.mjs';
15
- import { cleanupArtifactOnTaskSettled, removeTransportFileNow } from './bash-tool/transport-artifacts.mjs';
15
+ import {
16
+ cleanupArtifactAfter,
17
+ cleanupArtifactOnTaskSettled,
18
+ removeTransportFileNow,
19
+ } from './bash-tool/transport-artifacts.mjs';
16
20
  import { combineAbortSignals, sessionAbortSignal } from './bash-tool/abort-signals.mjs';
17
21
  import { beginInterruptibleTaskWait } from '../../session/task-wait-control.mjs';
18
22
  import { prepareShellCommand } from './bash-tool/command-preflight.mjs';
@@ -39,9 +43,10 @@ export { buildShellSpawnEnv } from './bash-tool/spawn-env.mjs';
39
43
  export { executeTaskTool } from './task-tool.mjs';
40
44
 
41
45
  // Reads cached after a mutating command must not serve pre-command state. A
42
- // promoted command has barely started when the tool returns and a queued one
43
- // has not started at all, so the invalidation repeats when it settles.
44
- function settleMutationCaches(shellEffects, backgroundJobId, queuedSettled = null) {
46
+ // promoted command has barely started when the tool returns, a queued one has
47
+ // not started at all and a killed tree may still run, so the invalidation
48
+ // repeats when it settles.
49
+ function settleMutationCaches(shellEffects, backgroundJobId, runSettled = null) {
45
50
  const invalidate = () => {
46
51
  if (shellEffects.mutationMode === 'paths') {
47
52
  invalidateBuiltinResultCache(shellEffects.paths);
@@ -55,7 +60,7 @@ function settleMutationCaches(shellEffects, backgroundJobId, queuedSettled = nul
55
60
  if (backgroundJobId && shellEffects.mutationMode !== 'none') {
56
61
  subscribeShellJobSettled(backgroundJobId, invalidate);
57
62
  }
58
- if (queuedSettled && shellEffects.mutationMode !== 'none') queuedSettled.then(invalidate).catch(() => {});
63
+ if (runSettled && shellEffects.mutationMode !== 'none') runSettled.then(invalidate).catch(() => {});
59
64
  }
60
65
 
61
66
  export async function executeBashTool(args, workDir, options = {}) {
@@ -89,7 +94,8 @@ export async function executeBashTool(args, workDir, options = {}) {
89
94
  const inputWake = backgroundOnTimeout ? beginInterruptibleTaskWait(options?.sessionId) : null;
90
95
  let backgrounded = false;
91
96
  let backgroundJobId = null;
92
- let queuedSettled = null;
97
+ // A queued run, or a foreground tree that may outlive its result, settles later.
98
+ let runSettled = null;
93
99
  try {
94
100
  const invocation = await planShellInvocation({ command, resolvedSpec, cwd: bashWorkDir });
95
101
  if (invocation.failure) return invocation.failure;
@@ -127,7 +133,7 @@ export async function executeBashTool(args, workDir, options = {}) {
127
133
  });
128
134
  backgrounded = result.backgrounded === true;
129
135
  backgroundJobId = backgrounded ? result.jobId || null : null;
130
- queuedSettled = backgrounded ? result.settled || null : null;
136
+ runSettled = result.settled || null;
131
137
  const stdout = stripAnsi(result.stdout || '');
132
138
  const stderr = stripAnsi(result.stderr || '');
133
139
  recordShellCaptureTelemetry(options?.resultTelemetry, result, stdout, stderr);
@@ -152,14 +158,13 @@ export async function executeBashTool(args, workDir, options = {}) {
152
158
  inputWake?.dispose();
153
159
  combinedAbort?.cleanup?.();
154
160
  if (hoisted.hoistPath) {
155
- // A promoted or queued command still runs from the hoisted file; a
156
- // foreground one is done with it now (an unconfirmed kill may keep it
157
- // locked — retried).
158
- if (queuedSettled) {
159
- queuedSettled.then(() => removeTransportFileNow(hoisted.hoistPath)).catch(() => {});
160
- } else if (backgrounded) cleanupArtifactOnTaskSettled(hoisted.hoistPath, backgroundJobId);
161
+ // A promoted or queued command still runs from the hoisted file, and so
162
+ // may a killed tree until it settles; a finished foreground one is done
163
+ // with it now (an unconfirmed kill may keep it locked — retried).
164
+ if (runSettled) cleanupArtifactAfter(hoisted.hoistPath, runSettled);
165
+ else if (backgrounded) cleanupArtifactOnTaskSettled(hoisted.hoistPath, backgroundJobId);
161
166
  else removeTransportFileNow(hoisted.hoistPath);
162
167
  }
163
- settleMutationCaches(shellEffects, backgroundJobId, queuedSettled);
168
+ settleMutationCaches(shellEffects, backgroundJobId, runSettled);
164
169
  }
165
170
  }
@@ -87,7 +87,13 @@ export const BUILTIN_TOOLS = [
87
87
  type: 'integer',
88
88
  minimum: 1,
89
89
  maximum: PUBLIC_READ_WINDOW_MAX,
90
- description: 'Maximum line count; default 500 per exact file, 25 per glob-expanded file; batch default.',
90
+ description:
91
+ 'Maximum line count; default 500 per exact file, 25 per glob-expanded file; batch default. Lines only: PDFs use pages.',
92
+ },
93
+ pages: {
94
+ type: 'string',
95
+ description:
96
+ 'PDF page range "N" or "N-M", 1-based, ≤20 pages per call. Text pages return text; scanned pages (no text layer) return page images, or OCR text on a text-only model. Without pages, a PDF over 10 pages returns its first 5 pages and page count.',
91
97
  },
92
98
  },
93
99
  required: ['file_path'],
@@ -1,3 +1,4 @@
1
+ import { isUtf8 } from 'node:buffer';
1
2
  import { existsSync } from 'node:fs';
2
3
  import { mkdtemp, rm, writeFile } from 'node:fs/promises';
3
4
  import { tmpdir } from 'node:os';
@@ -8,6 +9,10 @@ import { withBuiltinPathLocks } from './path-locks.mjs';
8
9
  import { withAdvisoryLocks } from './advisory-lock.mjs';
9
10
  import { gitRepoLockKey, withGitWorktreeWriteLock } from './git-repo-rw-lock.mjs';
10
11
  import { invalidateBuiltinResultCache } from './cache-layers.mjs';
12
+ import { tryAdmitWorkspaceActivity } from '../../../../shared/worktrees/activity-gate.mjs';
13
+ import { WORKTREE_SNAPSHOT_KIND } from '../../../../shared/worktrees/contract.mjs';
14
+ import { isWithin } from '../../../../shared/worktrees/paths.mjs';
15
+ import { readRegistrySync } from '../../../../shared/worktrees/registry.mjs';
11
16
  import { runAbortable } from '../../../../shared/abort-race.mjs';
12
17
  import { sleep } from '../../../../shared/sleep.mjs';
13
18
  import { capMarker } from '../../../../shared/tool-output-marker.mjs';
@@ -26,6 +31,12 @@ import {
26
31
  diffSnapshotMatches,
27
32
  getDiffSnapshot,
28
33
  } from './git-partial-stage.mjs';
34
+ import {
35
+ executeRawWorktree,
36
+ executeWorktreeAction,
37
+ WORKTREE_INPUT_FIELDS,
38
+ WORKTREE_TOOL_ACTIONS,
39
+ } from './git-worktree-tool.mjs';
29
40
 
30
41
  const DEFAULT_TIMEOUT_MS = 120_000;
31
42
  const MAX_CAPTURE_BYTES = 128 * 1024 * 1024;
@@ -84,14 +95,15 @@ export const GIT_TOOL_DEF = {
84
95
  compressible: true,
85
96
  },
86
97
  description:
87
- 'Run Git here, never through shell. An array (max 10) runs in order and stops on failure; batch read-only commands in one. diff for known changes, status to discover them; history only when needed. git diff or git diff -- <paths> includes untracked files. Set include_stage_ids:true to select changes, then action:stage with diff_id/change_ids. Staging rejects stale or cross-Project diffs and returns per-file staged counts.',
98
+ 'Run Git here, never through shell. An array (max 10) runs in order and stops on failure; batch read-only commands in one. diff for known changes, status to discover them; history only when needed. git diff or git diff -- <paths> includes untracked files. Set include_stage_ids:true to select changes, then action:stage with diff_id/change_ids. Staging rejects stale or cross-Project diffs and returns per-file staged counts. Worktrees are managed only: action:worktree (Lead only) moves a session into/out of its managed worktree, merges, removes, lists, prunes; git worktree add/remove/list/prune map to managed operations (add -b keeps the session in place; add --detach makes a read-only snapshot worktree, removed when its turn ends; list --porcelain stays raw git; move/lock/unlock/repair are refused). Merge or remove only when the user asks; carry and large-size answers come from the user; force only on explicit request.',
88
99
  inputSchema: {
89
100
  type: 'object',
90
101
  properties: {
91
102
  action: {
92
103
  type: 'string',
93
- enum: ['command', 'stage'],
94
- description: 'Default command. stage uses diff_id/change_ids instead of command.',
104
+ enum: ['command', 'stage', 'worktree'],
105
+ description:
106
+ 'Default command. stage uses diff_id/change_ids instead of command. worktree uses worktree_action and its fields.',
95
107
  },
96
108
  command: {
97
109
  anyOf: [
@@ -118,6 +130,29 @@ export const GIT_TOOL_DEF = {
118
130
  anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 50 }],
119
131
  description: 'stage: exact change ID or IDs to stage, including new files.',
120
132
  },
133
+ worktree_action: {
134
+ type: 'string',
135
+ enum: [...WORKTREE_TOOL_ACTIONS],
136
+ description:
137
+ 'enter: create or re-enter the managed worktree (branch: that branch, create: new); return: back to the project folder, worktree kept; merge: into its merge target; remove; list; prune.',
138
+ },
139
+ session_id: {
140
+ type: 'string',
141
+ description: 'enter/return/merge/remove: another session (it must be idle); default the caller.',
142
+ },
143
+ name: { type: 'string', description: 'enter: the new worktree name (branch <prefix><name>).' },
144
+ branch: { type: 'string', description: 'enter: use the worktree of this branch instead.' },
145
+ create: { type: 'boolean', description: 'enter with branch: create that branch.' },
146
+ carry: {
147
+ type: 'string',
148
+ enum: ['leave', 'carry'],
149
+ description: 'enter: the user answer to WORKTREE_DIRTY_CHOICE (uncommitted project-folder changes).',
150
+ },
151
+ confirm_large: {
152
+ type: 'boolean',
153
+ description: 'enter or git worktree add: the user accepted the WORKTREE_LARGE_REPO_CONFIRM size.',
154
+ },
155
+ force: { type: 'boolean', description: 'remove: discard dirty/unmerged work; only on explicit user request.' },
121
156
  },
122
157
  additionalProperties: false,
123
158
  },
@@ -136,6 +171,9 @@ function ok(data = {}) {
136
171
  });
137
172
  }
138
173
 
174
+ const NOT_UTF8_STAGE_NOTE =
175
+ 'staging ids unavailable: this diff has bytes that are not UTF-8 (a file in another encoding), and staging its text would replace them; stage those files whole with git add -- <paths>';
176
+
139
177
  function fail(message) {
140
178
  return `error: ${message}`;
141
179
  }
@@ -398,6 +436,9 @@ async function runProcess(program, argv, { cwd, signal, maxBytes = MAX_CAPTURE_B
398
436
  ...(optionalLocks ? {} : { GIT_OPTIONAL_LOCKS: '0' }),
399
437
  },
400
438
  outputLimit: maxBytes,
439
+ // Bytes as git wrote them, decoded once by the caller: a multibyte
440
+ // character split across two pipe reads must not become U+FFFD.
441
+ rawOutput: true,
401
442
  },
402
443
  });
403
444
  if (!native?.child)
@@ -561,9 +602,13 @@ function prepare(plan) {
561
602
  return { argv: [operation, ...args] };
562
603
  }
563
604
 
605
+ // A stage applies the text of this diff, so a patch with bytes that are not
606
+ // UTF-8 (a legacy-encoded file) would be applied with U+FFFD in their place.
607
+ // `notUtf8` marks such a diff: it is shown, but never staged by id.
564
608
  async function runStageableDiff(plan, argv, repo, signal) {
565
609
  const result = await runGit(plan, argv, { signal, readOnly: true });
566
610
  if (!succeeded(result)) return result;
611
+ result.notUtf8 = !isUtf8(result.stdout);
567
612
  result.stdout = String(result.stdout);
568
613
  result.stderr = String(result.stderr);
569
614
  const paths = plan.args.slice(1);
@@ -593,6 +638,7 @@ async function runStageableDiff(plan, argv, repo, signal) {
593
638
  );
594
639
  // --no-index uses exit 1 for a successfully produced patch.
595
640
  if (!succeeded({ ...added, exitCode: added.exitCode === 1 ? 0 : added.exitCode })) return added;
641
+ if (!isUtf8(added.stdout)) result.notUtf8 = true;
596
642
  result.stdout = appendText(result.stdout, String(added.stdout));
597
643
  result.stderr = appendText(result.stderr, String(added.stderr));
598
644
  }
@@ -763,26 +809,14 @@ function creationTarget(plan) {
763
809
  // --prune=now as unsafe beside a concurrent writer), so these hold the whole
764
810
  // repository.
765
811
  const REPO_WIDE_OPERATIONS = new Set(['gc', 'maintenance', 'prune', 'prune-packed', 'repack']);
766
- const WORKTREE_VALUE_FLAGS = new Set(['-b', '-B', '--reason']);
767
812
 
768
813
  // The worktrees a mutating plan touches; null holds the whole repository. A
769
- // write changes its cwd's worktree, except that `git worktree add/remove/move`
770
- // change the target worktree (plus the shared admin dir, which git itself
771
- // guards), so writes on different worktrees never wait for each other.
814
+ // write changes its cwd's worktree, so writes on different worktrees never
815
+ // wait for each other. (`git worktree` never runs as a git write here: it
816
+ // maps to managed operations, which take the repository lock themselves.)
772
817
  function writeScopes(plan, root) {
773
818
  if (REPO_WIDE_OPERATIONS.has(plan.operation)) return null;
774
- if (plan.operation !== 'worktree') return [root];
775
- const [action, ...paths] = optionFreePositionals(plan.args, WORKTREE_VALUE_FLAGS);
776
- const target = (path) => resolve(plan.cwd, path);
777
- // git also names a worktree by a unique path suffix, which no cwd-relative
778
- // path resolves; an argument that is not a worktree here holds the whole
779
- // repository instead of guessing (so do prune and an argument-less repair).
780
- const existing = (path) => (existsSync(join(target(path), '.git')) ? target(path) : null);
781
- let scopes = null;
782
- if (action === 'add') scopes = paths.slice(0, 1).map(target);
783
- else if (action === 'move' && paths.length === 2) scopes = [existing(paths[0]), target(paths[1])];
784
- else if (['remove', 'lock', 'unlock', 'repair'].includes(action)) scopes = paths.map(existing);
785
- return scopes?.length && scopes.every(Boolean) ? scopes : null;
819
+ return [root];
786
820
  }
787
821
 
788
822
  // A mutating git call holds the in-process and cross-process locks on its
@@ -804,6 +838,34 @@ function withGitWriteLocks(lockKey, scopes, root, signal, callback) {
804
838
  );
805
839
  }
806
840
 
841
+ // The managed snapshot worktree `repo` lies in, else null. A registry that
842
+ // cannot be read names none: commands elsewhere run as before.
843
+ function snapshotContaining(repo) {
844
+ let entries;
845
+ try {
846
+ entries = readRegistrySync().entries;
847
+ } catch {
848
+ return null;
849
+ }
850
+ return (
851
+ Object.values(entries).find(
852
+ (record) => record?.kind === WORKTREE_SNAPSHOT_KIND && typeof record.path === 'string' && isWithin(record.path, repo)
853
+ ) ?? null
854
+ );
855
+ }
856
+
857
+ // A command run in a snapshot worktree is one of its consumers: the workspace
858
+ // lease it holds (activity-gate, the same as a background shell's) keeps the
859
+ // snapshot from being removed in any process until it ends, and a snapshot
860
+ // whose removal is under way refuses it. Returns the release.
861
+ function admitSnapshotCommand(repo, sessionId) {
862
+ const snapshot = snapshotContaining(repo);
863
+ if (!snapshot) return () => {};
864
+ const leave = tryAdmitWorkspaceActivity(sessionId || 'git-tool', repo, { kind: 'command' });
865
+ if (!leave) throw new Error(`the snapshot worktree ${snapshot.path} is being removed; nothing ran`);
866
+ return leave;
867
+ }
868
+
807
869
  function invalidateAfterGitMutation() {
808
870
  invalidateBuiltinResultCache();
809
871
  drainCodeGraphCache();
@@ -873,6 +935,7 @@ async function executeGitStage(input, workDir, options = {}) {
873
935
  };
874
936
  const current = await runStageableDiff(snapshot.plan, snapshot.argv, repo, signal);
875
937
  if (!succeeded(current)) return noted(commandFailure(snapshot.plan, current, limit));
938
+ if (current.notUtf8) return noted(fail(NOT_UTF8_STAGE_NOTE));
876
939
  const raw = current.stdout;
877
940
  if (!diffSnapshotMatches(snapshot, raw)) {
878
941
  return answer({
@@ -919,6 +982,15 @@ async function executeSingleGitTool(input, workDir, options = {}) {
919
982
  } catch (error) {
920
983
  return rejected(error.message);
921
984
  }
985
+ // No unmanaged worktree is ever made here: worktree commands run as their
986
+ // managed operations (git-worktree-tool.mjs); only `list --porcelain` stays git.
987
+ if (plan.operation === 'worktree') {
988
+ const mapped = await executeRawWorktree(plan, input, options);
989
+ if (mapped) {
990
+ if (!isReadOnly(plan)) invalidateAfterGitMutation();
991
+ return mapped;
992
+ }
993
+ }
922
994
  if (
923
995
  plan.operation === 'archive' &&
924
996
  !plan.args.some((value) => value === '-o' || value === '--output' || value.startsWith('--output='))
@@ -954,6 +1026,23 @@ async function executeSingleGitTool(input, workDir, options = {}) {
954
1026
  const missing = probe ?? (await runGit(plan, ['rev-parse', '--show-toplevel'], { signal }));
955
1027
  return commandResult(plan, missing, limit);
956
1028
  }
1029
+ // A command in a snapshot worktree uses it like a worker would: it holds a
1030
+ // workspace lease there while it runs, so the snapshot is never removed
1031
+ // under it, and a snapshot being removed refuses it.
1032
+ let leaveSnapshot;
1033
+ try {
1034
+ leaveSnapshot = admitSnapshotCommand(repo, options.sessionId);
1035
+ } catch (error) {
1036
+ return rejected(error.message);
1037
+ }
1038
+ try {
1039
+ return await runInRepository(input, plan, { repo, lockKey, limit, signal, workDir });
1040
+ } finally {
1041
+ leaveSnapshot();
1042
+ }
1043
+ }
1044
+
1045
+ async function runInRepository(input, plan, { repo, lockKey, limit, signal, workDir }) {
957
1046
  const prepared = prepare(plan);
958
1047
  const stageableRequest = plan.operation === 'diff' && (plan.args.length === 0 || plan.args[0] === '--');
959
1048
  if (stageableRequest) prepared.argv.splice(1, 0, '--no-textconv', '--src-prefix=a/', '--dst-prefix=b/');
@@ -965,6 +1054,11 @@ async function executeSingleGitTool(input, workDir, options = {}) {
965
1054
  : await runGit(plan, prepared.argv, { signal, readOnly: true });
966
1055
  if (!succeeded(result)) return commandResult(plan, result, limit);
967
1056
  if (stageableRequest && input.include_stage_ids === true) {
1057
+ if (result.notUtf8) {
1058
+ const rendered = commandResult(plan, result, limit);
1059
+ rendered.text = appendText(rendered.text, NOT_UTF8_STAGE_NOTE);
1060
+ return rendered;
1061
+ }
968
1062
  const snapshot = createDiffSnapshot({
969
1063
  repo,
970
1064
  scope: resolve(workDir || process.cwd()),
@@ -977,6 +1071,9 @@ async function executeSingleGitTool(input, workDir, options = {}) {
977
1071
  return commandResult(plan, result, limit);
978
1072
  }
979
1073
  return withGitWriteLocks(lockKey, writeScopes(plan, repo), repo, signal, async (waitedMs) => {
1074
+ // A managed removal holds the same folder lock while it deletes: once it
1075
+ // is ours, a folder that is gone was removed, and nothing runs there.
1076
+ if (!existsSync(repo)) return rejected(`the worktree folder ${repo} was removed meanwhile; nothing ran`);
980
1077
  const result = await runGitWrite(plan, prepared.argv, { signal });
981
1078
  invalidateAfterGitMutation();
982
1079
  const rendered = commandResult(plan, result, limit);
@@ -1064,13 +1161,23 @@ export async function executeGitTool(rawInput, workDir, options = {}) {
1064
1161
  if (rawInput.change_ids !== undefined) input.change_ids = parseStringifiedArray(rawInput.change_ids);
1065
1162
  if (rawInput.command === undefined) delete input.command;
1066
1163
  const action = input.action ?? 'command';
1164
+ if (action === 'worktree') {
1165
+ const text = await executeWorktreeAction(input, workDir, options);
1166
+ if (input.worktree_action !== 'list') invalidateAfterGitMutation();
1167
+ return text;
1168
+ }
1169
+ const worktreeField = WORKTREE_INPUT_FIELDS.find((field) => input[field] !== undefined);
1170
+ if (worktreeField) return fail(`git ${worktreeField} requires action:worktree`);
1171
+ if (input.confirm_large !== undefined && action !== 'command') {
1172
+ return fail('git confirm_large applies to action:worktree or a git worktree add command');
1173
+ }
1067
1174
  if (action === 'stage') {
1068
1175
  if (input.command !== undefined || input.include_stage_ids !== undefined) {
1069
1176
  return fail('git action:stage accepts diff_id/change_ids, not command/include_stage_ids');
1070
1177
  }
1071
1178
  return executeGitStage(input, workDir, options);
1072
1179
  }
1073
- if (action !== 'command') return fail('git action must be command or stage');
1180
+ if (action !== 'command') return fail('git action must be command, stage or worktree');
1074
1181
  if (input.diff_id !== undefined || input.change_ids !== undefined) {
1075
1182
  return fail('git diff_id/change_ids require action:stage');
1076
1183
  }