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
@@ -0,0 +1,557 @@
1
+ /**
2
+ * The git tool's managed-worktree surface. Agents never create unmanaged
3
+ * worktrees: `action: "worktree"` drives the Lead session's managed worktree
4
+ * (the desktop branch menu's operations, same checks and refusals), and a raw
5
+ * `git worktree …` command maps onto a managed operation instead of running:
6
+ *
7
+ * add [-b <branch>] <path> [<commit-ish>] → managed worktree for that branch; the session stays put
8
+ * add --detach <path> [<commit>] → read-only snapshot, removed when the turn ends
9
+ * add <path> <commit> (not a branch) → the same snapshot (git would detach there too)
10
+ * remove [-f] <path> → managed removal of that worktree (same guards)
11
+ * list → managed worktrees (owners) plus unmanaged ones, flagged
12
+ * list --porcelain → raw git, unchanged (machine-readable truth)
13
+ * prune [-n] → managed prune (dry run: what it would remove)
14
+ * move | lock | unlock | repair → refused: managed locations are fixed
15
+ *
16
+ * Every operation but `list` belongs to the Lead: the session runtime that
17
+ * hosts the caller registers its host (shared/worktrees/tool-host.mjs); a
18
+ * caller no host claims (a delegated worker) is refused.
19
+ */
20
+ import { basename, resolve } from 'node:path';
21
+ import { isPlainObject } from '../../../../shared/object.mjs';
22
+ import { WORKTREE_REFUSAL_REASONS } from '../../../../shared/worktrees/contract.mjs';
23
+ import { firstLine, listWorktrees, runGit } from '../../../../shared/worktrees/git.mjs';
24
+ import { canonicalPath, samePath } from '../../../../shared/worktrees/paths.mjs';
25
+ import { worktreeToolHost } from '../../../../shared/worktrees/tool-host.mjs';
26
+
27
+ export const WORKTREE_TOOL_ACTIONS = Object.freeze(['enter', 'return', 'merge', 'remove', 'list', 'prune']);
28
+ const CARRY_CHOICES = ['leave', 'carry'];
29
+ // The input fields of action:"worktree" (confirm_large also answers a mapped `git worktree add`).
30
+ export const WORKTREE_INPUT_FIELDS = Object.freeze([
31
+ 'worktree_action',
32
+ 'session_id',
33
+ 'name',
34
+ 'branch',
35
+ 'create',
36
+ 'carry',
37
+ 'force',
38
+ ]);
39
+
40
+ const ADD_BARE_FLAGS = new Set([
41
+ '-q',
42
+ '--quiet',
43
+ '-f',
44
+ '--force',
45
+ '--lock',
46
+ '--checkout',
47
+ '--track',
48
+ '--no-track',
49
+ '--guess-remote',
50
+ '--no-guess-remote',
51
+ ]);
52
+ const FIXED_LOCATION_SUBCOMMANDS = new Set(['move', 'lock', 'unlock', 'repair']);
53
+
54
+ const fail = (message) => `error: ${message}`;
55
+ const okJson = (data) => JSON.stringify({ ok: true, ...data });
56
+
57
+ /** The desktop's byte size text (worktree-branch-model.ts formatByteSize). */
58
+ function formatByteSize(bytes) {
59
+ if (!(bytes > 0)) return '';
60
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
61
+ let value = bytes;
62
+ let unit = 0;
63
+ while (value >= 1024 && unit < units.length - 1) {
64
+ value /= 1024;
65
+ unit += 1;
66
+ }
67
+ return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
68
+ }
69
+
70
+ /** The runtime's own text after the last `<code>: ` prefix. */
71
+ function runtimeText(code, message) {
72
+ const marker = `${code}: `;
73
+ const index = message.lastIndexOf(marker);
74
+ return (index >= 0 ? message.slice(index + marker.length) : message).trim();
75
+ }
76
+
77
+ /**
78
+ * The desktop's wording of a typed failure (worktree-branch-model.ts
79
+ * worktreeFailureText, English source strings); the runtime's message where
80
+ * the desktop has none.
81
+ */
82
+ function worktreeFailureText(code, message, details) {
83
+ switch (code) {
84
+ case 'WORKTREE_DISK_INSUFFICIENT': {
85
+ const required = formatByteSize(Number(details.requiredBytes));
86
+ const free = formatByteSize(Number(details.freeBytes));
87
+ return required && free
88
+ ? `Not enough free disk space to create the worktree (${required} needed, ${free} free).`
89
+ : 'Not enough free disk space to create the worktree.';
90
+ }
91
+ case 'WORKTREE_FILE_RECOVERY':
92
+ return 'Retained file recovery records block worktree removal, including force. Inspect and explicitly delete them first.';
93
+ case 'WORKTREE_BUSY_JOBS':
94
+ return (
95
+ WORKTREE_REFUSAL_REASONS[details.reason] ??
96
+ (details.reason === 'snapshot-held'
97
+ ? message
98
+ : 'Background tasks are still running. Wait for them or stop them, then try again.')
99
+ );
100
+ case 'WORKTREE_BRANCH_OCCUPIED':
101
+ return typeof details.path === 'string' && details.path
102
+ ? `This branch is already checked out in ${details.path}.`
103
+ : 'This branch is already checked out in another worktree.';
104
+ case 'WORKTREE_REMOVAL_INCOMPLETE':
105
+ return 'The worktree could not be removed completely because a file is still in use. Close it and remove the worktree again.';
106
+ case 'WORKTREE_PATH_UNSAFE':
107
+ return 'The worktree folder is not a managed worktree of this project, so nothing was deleted.';
108
+ case 'WORKTREE_MISSING':
109
+ return 'The worktree folder is missing. Pick a project branch to continue.';
110
+ case 'WORKTREE_REVISION_CONFLICT':
111
+ return 'The workspace changed, so the menu was refreshed. Try again.';
112
+ case 'WORKTREE_NOT_IMPLEMENTED':
113
+ return 'Worktrees are not available yet.';
114
+ case 'WORKTREE_UNSUPPORTED_REPO':
115
+ return 'This folder does not support worktrees.';
116
+ case 'WORKTREE_DIRTY': {
117
+ // A refused carry names its own reason in the runtime's text.
118
+ if (details.reason && details.reason !== 'project-dirty') return runtimeText(code, message);
119
+ if (details.reason === 'project-dirty') {
120
+ return 'The project folder has uncommitted changes. Commit or stash them before switching branches.';
121
+ }
122
+ return 'The worktree has uncommitted changes.';
123
+ }
124
+ case 'WORKTREE_PARTIAL_TRANSITION':
125
+ return runtimeText(code, message);
126
+ case 'WORKTREE_PROJECT_UNKNOWN':
127
+ return 'Project is not available.';
128
+ case 'WORKTREE_PROJECT_MISMATCH':
129
+ return 'This session keeps a worktree of another project. Remove it before creating a new one here.';
130
+ case 'WORKTREE_UNMERGED':
131
+ return 'The worktree branch has commits that are not merged.';
132
+ case 'MERGE_TARGET_DIRTY':
133
+ return 'The branch to merge into has uncommitted changes. Commit or stash them first.';
134
+ case 'WORKTREE_CONFIRM_REQUIRED':
135
+ return 'Confirm the merge before it runs.';
136
+ case 'WORKTREE_TARGET_MISMATCH':
137
+ return 'The project folder switched to another branch. Check the target and merge again.';
138
+ case 'MERGE_FAILED': {
139
+ const reason = runtimeText(code, message);
140
+ return reason ? `The merge failed: ${reason}` : 'The merge failed.';
141
+ }
142
+ case 'MERGE_CONFLICT': {
143
+ const files = Array.isArray(details.files) ? details.files.map(String) : [];
144
+ if (!files.length) return 'Nothing was merged because the branches conflict.';
145
+ return `Nothing was merged. Conflicting files: ${files.slice(0, 5).join(', ')}${files.length > 5 ? ', …' : ''}`;
146
+ }
147
+ default:
148
+ return message;
149
+ }
150
+ }
151
+
152
+ /** What the caller does next, for the refusals that need the user or a retry. */
153
+ function nextStep(code, details, { raw }) {
154
+ switch (code) {
155
+ case 'WORKTREE_DIRTY_CHOICE':
156
+ return `Ask the user whether to leave the ${details.count ?? 'uncommitted'} uncommitted change(s) in the project folder or carry them into the worktree; then call again with carry:"leave" or carry:"carry".`;
157
+ case 'WORKTREE_LARGE_REPO_CONFIRM': {
158
+ const size = formatByteSize(Number(details.estimateBytes));
159
+ return `Tell the user the worktree needs about ${size || 'a lot of disk space'} and ask to confirm; only on yes call again with confirm_large:true${raw ? ' (same command)' : ''}.`;
160
+ }
161
+ case 'WORKTREE_REVISION_CONFLICT':
162
+ return 'The workspace changed meanwhile; call again (the workspace is read afresh).';
163
+ case 'WORKTREE_BUSY_JOBS':
164
+ return 'Nothing was changed; try again once the session and its workers are idle.';
165
+ case 'MERGE_CONFLICT':
166
+ return 'Nothing was merged or forced. Resolve the conflict in the worktree (merge the target branch into it there), or ask the user.';
167
+ case 'WORKTREE_DIRTY':
168
+ case 'WORKTREE_UNMERGED':
169
+ return 'Nothing was removed. Commit or merge the work first; force only when the user explicitly asks to discard it.';
170
+ default:
171
+ return null;
172
+ }
173
+ }
174
+
175
+ /** A failed operation for the model: typed failures as JSON with their details, others as text. */
176
+ function failureText(error, { raw = false } = {}) {
177
+ const code = typeof error?.code === 'string' ? error.code : '';
178
+ const message = String(error?.message || error);
179
+ if (!/^(?:WORKTREE|MERGE)_/.test(code)) return fail(message);
180
+ const details = isPlainObject(error.details) ? error.details : {};
181
+ const next = nextStep(code, details, { raw });
182
+ return JSON.stringify({
183
+ ok: false,
184
+ code,
185
+ error: worktreeFailureText(code, message, details),
186
+ ...(Object.keys(details).length ? { details } : {}),
187
+ ...(next ? { next } : {}),
188
+ });
189
+ }
190
+
191
+ function compactWorkspace(workspace) {
192
+ if (!workspace) return null;
193
+ return {
194
+ cwd: workspace.cwd,
195
+ branch: workspace.branch ?? null,
196
+ mergeTarget: workspace.mergeTarget ?? null,
197
+ revision: workspace.revision,
198
+ worktreeId: workspace.worktreeId ?? null,
199
+ };
200
+ }
201
+
202
+ /** A workspace operation's result: its own fields, the workspace compacted. */
203
+ function transitionResult(fields, result) {
204
+ const { workspace, revision: _revision, pending: _pending, ...rest } = isPlainObject(result) ? result : {};
205
+ return okJson({ ...fields, ...rest, ...(workspace ? { workspace: compactWorkspace(workspace) } : {}) });
206
+ }
207
+
208
+ /** The Lead host of the caller; a caller none claims is no Lead. */
209
+ function leadHost(options, what) {
210
+ const host = worktreeToolHost(options?.sessionId);
211
+ if (!host) {
212
+ throw new Error(`${what} belongs to the Lead session; delegated workers cannot create, change or remove worktrees`);
213
+ }
214
+ return host;
215
+ }
216
+
217
+ const runOn = (host, request, options) => host.run(request, { callerSessionId: options.sessionId });
218
+
219
+ /** The transaction id of one operation of this tool call (stable across a retry of the call). */
220
+ function txIdOf(options, discriminator) {
221
+ const id = String(options?.toolCallId || '').trim();
222
+ return id ? `${id}:${discriminator}` : '';
223
+ }
224
+
225
+ /** Repository facts in `cwd` from git itself (null outside a repository). */
226
+ async function repositoryAt(cwd) {
227
+ const probe = await runGit(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir'], { allowFailure: true });
228
+ return probe.code === 0 ? { repoCommonDir: canonicalPath(firstLine(probe.stdout)) } : null;
229
+ }
230
+
231
+ async function localBranchExists(cwd, branch) {
232
+ const result = await runGit(cwd, ['rev-parse', '--verify', '--quiet', `refs/heads/${branch}`], {
233
+ allowFailure: true,
234
+ });
235
+ return result.code === 0;
236
+ }
237
+
238
+ /** Full commit id of commit-ish `value` resolved in `cwd`, or null. */
239
+ async function commitIn(cwd, value) {
240
+ if (!value || value.startsWith('-')) return null;
241
+ const result = await runGit(cwd, ['rev-parse', '--verify', '--quiet', `${value}^{commit}`], { allowFailure: true });
242
+ return result.code === 0 ? firstLine(result.stdout) : null;
243
+ }
244
+
245
+ /**
246
+ * Managed rows (the owners) of the repository at `cwd` (every repository
247
+ * outside one) plus the repository's other git worktrees: the main checkout
248
+ * and unmanaged ones, flagged.
249
+ */
250
+ async function listRows(options, cwd) {
251
+ const host = worktreeToolHost(options?.sessionId);
252
+ const managed = host
253
+ ? await runOn(host, { op: 'list' }, options)
254
+ : await (await import('../../../../shared/worktrees/manager.mjs')).listManagedWorktrees();
255
+ const repo = await repositoryAt(cwd);
256
+ const ours = repo ? managed.filter((row) => samePath(row.repoCommonDir, repo.repoCommonDir)) : managed;
257
+ const rows = ours.map((row) => ({
258
+ id: row.id,
259
+ path: row.path,
260
+ branch: row.branch ?? null,
261
+ ...(row.kind === 'snapshot'
262
+ ? { snapshot: true, commit: row.baseCommit, holderSessionId: row.holderSessionId ?? null }
263
+ : { ownerSessionId: row.ownerSessionId ?? null, mergeTarget: row.mergeTarget ?? null }),
264
+ state: row.state,
265
+ ...(typeof row.ownerLive === 'boolean' ? { ownerLive: row.ownerLive } : {}),
266
+ ...(host && typeof row.removable === 'boolean' ? { removable: row.removable } : {}),
267
+ }));
268
+ if (!repo) return { worktrees: rows };
269
+ // git lists the main working tree first.
270
+ const [main, ...linked] = await listWorktrees(cwd);
271
+ const unmanaged = linked
272
+ .filter((worktree) => !ours.some((row) => samePath(row.path, worktree.path)))
273
+ .map((worktree) => ({
274
+ path: worktree.path,
275
+ branch: worktree.branch,
276
+ ...(worktree.detached ? { head: worktree.head } : {}),
277
+ unmanaged: true,
278
+ ...(worktree.prunable ? { prunable: true } : {}),
279
+ }));
280
+ return { worktrees: [{ path: main.path, branch: main.branch, main: true }, ...rows, ...unmanaged] };
281
+ }
282
+
283
+ /**
284
+ * `git worktree add`: a managed worktree for a branch (the session does not
285
+ * move), or a read-only snapshot for a detached commit. The path argument is
286
+ * never used; the result names the managed path.
287
+ */
288
+ async function rawAdd(plan, input, options) {
289
+ let branch = null;
290
+ let reset = false;
291
+ let detach = false;
292
+ const positional = [];
293
+ const args = plan.args.slice(1);
294
+ for (let index = 0; index < args.length; index++) {
295
+ const value = args[index];
296
+ if (value === '--') {
297
+ positional.push(...args.slice(index + 1));
298
+ break;
299
+ }
300
+ if (value === '-b' || value === '-B') {
301
+ branch = args[++index];
302
+ reset = value === '-B';
303
+ if (!branch) throw new Error(`git worktree add: ${value} requires a branch name`);
304
+ continue;
305
+ }
306
+ if (value === '--reason') {
307
+ index++;
308
+ continue;
309
+ }
310
+ if (value.startsWith('--reason=')) continue;
311
+ if (value === '--detach' || value === '-d') {
312
+ detach = true;
313
+ continue;
314
+ }
315
+ if (value === '--orphan' || value === '--no-checkout') {
316
+ throw new Error(`git worktree add: ${value} is not supported for managed worktrees`);
317
+ }
318
+ if (ADD_BARE_FLAGS.has(value)) continue;
319
+ if (value.startsWith('-')) throw new Error(`git worktree add: option ${value} is not supported for managed worktrees`);
320
+ positional.push(value);
321
+ }
322
+ const [path, commitish = null] = positional;
323
+ if (!path || positional.length > 2) throw new Error('git worktree add requires <path> [<commit-ish>]');
324
+ if (detach && branch) throw new Error('git worktree add: --detach excludes -b/-B');
325
+ const host = leadHost(options, 'git worktree add');
326
+ const repo = await repositoryAt(plan.cwd);
327
+ if (!repo) throw new Error(`git worktree add: ${plan.cwd} is not a git repository`);
328
+ const confirmLarge = input.confirm_large === true;
329
+ const txId = txIdOf(options, plan.command);
330
+ const mapped = { mapped: plan.command, requestedPath: resolve(plan.cwd, path) };
331
+ // A detached checkout, or a commit-ish that names no local branch: a snapshot.
332
+ const snapshotCommit = detach
333
+ ? (commitish ?? 'HEAD')
334
+ : !branch && commitish && !(await localBranchExists(plan.cwd, commitish))
335
+ ? commitish
336
+ : null;
337
+ if (snapshotCommit) {
338
+ const commit = await commitIn(plan.cwd, snapshotCommit);
339
+ if (!commit) throw new Error(`git worktree add: ${snapshotCommit} is not a commit`);
340
+ const entry = await runOn(
341
+ host,
342
+ { op: 'snapshot', commit, confirmLarge, txId, repoCommonDir: repo.repoCommonDir },
343
+ options
344
+ );
345
+ return okJson({
346
+ ...mapped,
347
+ snapshot: true,
348
+ path: entry.path,
349
+ commit: entry.baseCommit,
350
+ worktreeId: entry.id,
351
+ note: 'Managed read-only snapshot at the path above (not the one requested); it is removed when this turn ends, and changes made in it are discarded. The session did not move.',
352
+ });
353
+ }
354
+ let create;
355
+ let startPoint = null;
356
+ if (branch) {
357
+ const exists = await localBranchExists(plan.cwd, branch);
358
+ if (exists) {
359
+ throw new Error(
360
+ reset
361
+ ? `git worktree add: -B would reset the existing branch ${branch}; managed worktrees never reset a branch (omit -B to check it out as it is)`
362
+ : `git worktree add: a branch named '${branch}' already exists (omit -b to check it out)`
363
+ );
364
+ }
365
+ create = true;
366
+ startPoint = await commitIn(plan.cwd, commitish ?? 'HEAD');
367
+ if (!startPoint) throw new Error(`git worktree add: ${commitish} is not a commit`);
368
+ } else {
369
+ // git's convenience: no branch named, so the branch is the commit-ish
370
+ // (an existing local branch) or the path's last segment.
371
+ branch = commitish ?? basename(resolve(plan.cwd, path));
372
+ create = !(await localBranchExists(plan.cwd, branch));
373
+ if (create) startPoint = await commitIn(plan.cwd, 'HEAD');
374
+ }
375
+ const added = await runOn(
376
+ host,
377
+ { op: 'add', branch, create, startPoint, confirmLarge, txId, repoCommonDir: repo.repoCommonDir },
378
+ options
379
+ );
380
+ return okJson({
381
+ ...mapped,
382
+ ...added,
383
+ note: `Managed worktree at the path above (not the one requested), retained by this session. The session did not move; worktree_action:"enter" with branch:"${branch}" moves it there.`,
384
+ });
385
+ }
386
+
387
+ async function rawRemove(plan, options) {
388
+ let force = false;
389
+ const positional = [];
390
+ for (const value of plan.args.slice(1)) {
391
+ if (value === '-f' || value === '--force') force = true;
392
+ else if (value.startsWith('-')) throw new Error(`git worktree remove: option ${value} is not supported`);
393
+ else positional.push(value);
394
+ }
395
+ if (positional.length !== 1) throw new Error('git worktree remove requires exactly one <path>');
396
+ const host = leadHost(options, 'git worktree remove');
397
+ const path = resolve(plan.cwd, positional[0]);
398
+ const result = await runOn(host, { op: 'remove-path', path, force }, options);
399
+ return transitionResult({ mapped: plan.command, path }, result);
400
+ }
401
+
402
+ /** `git worktree prune` arguments, all validated before anything runs. */
403
+ function parsePruneArgs(args) {
404
+ let dryRun = false;
405
+ for (const value of args) {
406
+ if (value === '-n' || value === '--dry-run') dryRun = true;
407
+ else if (value === '-v' || value === '--verbose') continue;
408
+ else if (value.startsWith('-')) {
409
+ throw new Error(
410
+ `git worktree prune: option ${value} is not supported (managed prune takes -n/--dry-run and -v/--verbose)`
411
+ );
412
+ } else throw new Error(`git worktree prune: unexpected argument ${value}`);
413
+ }
414
+ return { dryRun };
415
+ }
416
+
417
+ /**
418
+ * The prune and its dry run share one scope: the repository of the command's
419
+ * directory (every repository outside one), so the preview names exactly what
420
+ * the real prune may touch.
421
+ */
422
+ async function rawPrune(plan, options) {
423
+ const { dryRun } = parsePruneArgs(plan.args.slice(1));
424
+ const host = leadHost(options, 'git worktree prune');
425
+ if (dryRun) {
426
+ const { worktrees } = await listRows(options, plan.cwd);
427
+ return okJson({ mapped: plan.command, dryRun: true, wouldRemove: worktrees.filter((row) => row.removable) });
428
+ }
429
+ const repo = await repositoryAt(plan.cwd);
430
+ return okJson({
431
+ mapped: plan.command,
432
+ ...(await runOn(host, { op: 'prune', repoCommonDir: repo?.repoCommonDir ?? null }, options)),
433
+ });
434
+ }
435
+
436
+ const WORKTREE_HELP = Object.freeze({
437
+ add: 'git worktree add [-b <branch>] [--detach] <path> [<commit-ish>]: a managed worktree for the branch (the session stays put), or a read-only snapshot for a detached commit.',
438
+ remove: 'git worktree remove [-f] <path>: the managed removal of that worktree.',
439
+ list: 'git worktree list [-v]: managed worktrees plus the repository\'s other worktrees. --porcelain stays raw git.',
440
+ prune: 'git worktree prune [-n|--dry-run] [-v]: the managed prune of this repository (clean, merged worktrees no session retains); -n only lists what it would remove.',
441
+ });
442
+
443
+ /** -h/--help before any `--` (the help answers instead of the operation running). */
444
+ function wantsHelp(args) {
445
+ const end = args.indexOf('--');
446
+ return (end < 0 ? args : args.slice(0, end)).some((value) => value === '-h' || value === '--help');
447
+ }
448
+
449
+ /** `git worktree list` takes no managed option beyond -v; the rest is validated, not ignored. */
450
+ function checkListArgs(args) {
451
+ for (const value of args) {
452
+ if (value === '-v' || value === '--verbose') continue;
453
+ throw new Error(`git worktree list: ${value.startsWith('-') ? `option ${value} is not supported` : `unexpected argument ${value}`}`);
454
+ }
455
+ }
456
+
457
+ /**
458
+ * A raw `git worktree …` command as its managed operation: `{ text, failed }`,
459
+ * or null for the one form that still runs as git (`list --porcelain`).
460
+ */
461
+ export async function executeRawWorktree(plan, input, options = {}) {
462
+ const sub = String(plan.args[0] || '');
463
+ if (WORKTREE_HELP[sub] && wantsHelp(plan.args.slice(1))) {
464
+ return { text: okJson({ mapped: plan.command, help: WORKTREE_HELP[sub] }), failed: false };
465
+ }
466
+ if (sub === 'list' && plan.args.includes('--porcelain')) return null;
467
+ try {
468
+ if (sub === 'list') checkListArgs(plan.args.slice(1));
469
+ if (sub === 'list') return { text: okJson({ mapped: plan.command, ...(await listRows(options, plan.cwd)) }), failed: false };
470
+ if (sub === 'add') return { text: await rawAdd(plan, input, options), failed: false };
471
+ if (sub === 'remove') return { text: await rawRemove(plan, options), failed: false };
472
+ if (sub === 'prune') return { text: await rawPrune(plan, options), failed: false };
473
+ if (FIXED_LOCATION_SUBCOMMANDS.has(sub)) {
474
+ return {
475
+ text: fail(
476
+ `git worktree ${sub} is refused: Mixdog-managed worktrees stay at their fixed, locked locations. Use git worktree list/remove/prune or action:"worktree"`
477
+ ),
478
+ failed: true,
479
+ };
480
+ }
481
+ return {
482
+ text: fail(`git worktree ${sub || '(no subcommand)'} is not supported; use add, remove, list, prune or action:"worktree"`),
483
+ failed: true,
484
+ };
485
+ } catch (error) {
486
+ return { text: failureText(error, { raw: true }), failed: true };
487
+ }
488
+ }
489
+
490
+ /** Validation of action:"worktree" input; the problem text, or null. */
491
+ function worktreeInputError(input) {
492
+ const action = input.worktree_action;
493
+ if (!WORKTREE_TOOL_ACTIONS.includes(action)) {
494
+ return `git action:worktree requires worktree_action: ${WORKTREE_TOOL_ACTIONS.join(', ')}`;
495
+ }
496
+ for (const field of ['command', 'diff_id', 'change_ids', 'include_stage_ids', 'output_limit']) {
497
+ if (input[field] !== undefined) return `git action:worktree does not take ${field}`;
498
+ }
499
+ const only = (field, allowed) =>
500
+ input[field] !== undefined && !allowed.includes(action)
501
+ ? `${field} applies to worktree_action ${allowed.join(', ')}, not ${action}`
502
+ : null;
503
+ const misplaced =
504
+ only('session_id', ['enter', 'return', 'merge', 'remove']) ||
505
+ only('name', ['enter']) ||
506
+ only('branch', ['enter']) ||
507
+ only('create', ['enter']) ||
508
+ only('carry', ['enter']) ||
509
+ only('confirm_large', ['enter']) ||
510
+ only('force', ['remove']);
511
+ if (misplaced) return misplaced;
512
+ if (input.session_id !== undefined && (typeof input.session_id !== 'string' || !input.session_id.trim())) {
513
+ return 'session_id must be a session id';
514
+ }
515
+ if (input.create !== undefined && !input.branch) return 'create needs branch';
516
+ if (input.branch !== undefined && (typeof input.branch !== 'string' || !input.branch.trim())) {
517
+ return 'branch must be a branch name';
518
+ }
519
+ if (input.branch && (input.name !== undefined || input.carry !== undefined)) {
520
+ return 'branch excludes name and carry (the branch names the worktree; changes stay in the project folder)';
521
+ }
522
+ if (input.carry !== undefined && !CARRY_CHOICES.includes(input.carry)) return 'carry must be leave or carry';
523
+ return null;
524
+ }
525
+
526
+ /** action:"worktree": one managed-worktree operation of the caller's (or `session_id`'s) session. */
527
+ export async function executeWorktreeAction(input, workDir, options = {}) {
528
+ const problem = worktreeInputError(input);
529
+ if (problem) return fail(problem);
530
+ const action = input.worktree_action;
531
+ try {
532
+ if (action === 'list') return okJson({ worktree_action: action, ...(await listRows(options, resolve(workDir || process.cwd()))) });
533
+ const host = leadHost(options, `git worktree_action ${action}`);
534
+ const sessionId = input.session_id?.trim() || null;
535
+ const request = {
536
+ op: action,
537
+ ...(sessionId ? { sessionId } : {}),
538
+ ...(action === 'enter'
539
+ ? {
540
+ name: input.name,
541
+ branch: input.branch?.trim() || null,
542
+ create: input.create === true,
543
+ carry: input.carry,
544
+ confirmLarge: input.confirm_large === true,
545
+ }
546
+ : {}),
547
+ ...(action === 'remove' ? { force: input.force === true } : {}),
548
+ ...(action === 'prune' ? { repoCommonDir: (await repositoryAt(resolve(workDir || process.cwd())))?.repoCommonDir ?? null } : {}),
549
+ txId: txIdOf(options, action),
550
+ };
551
+ const result = await runOn(host, request, options);
552
+ if (action === 'prune') return okJson({ worktree_action: action, ...result });
553
+ return transitionResult({ worktree_action: action, ...(sessionId ? { sessionId } : {}) }, result);
554
+ } catch (error) {
555
+ return failureText(error);
556
+ }
557
+ }
@@ -24,7 +24,7 @@ import { access, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/p
24
24
  import { unlinkSync } from 'node:fs';
25
25
  import { homedir } from 'node:os';
26
26
  import { join } from 'node:path';
27
- import { writeJsonAtomicAsync } from '../../../../../shared/atomic-file.mjs';
27
+ import { holdsNoData, writeJsonAtomicAsync } from '../../../../../shared/atomic-file.mjs';
28
28
  import { isPidAlive } from '../../../../../shared/pid-liveness.mjs';
29
29
  import { displayShellCommand } from '../../../../../shared/shell-display.mjs';
30
30
  import { tryAdmitWorkspaceActivity } from '../../../../../shared/worktrees/activity-gate.mjs';
@@ -339,21 +339,15 @@ export function releaseShellJobWorkspace(jobId) {
339
339
  if (workspaceLeases.get(key) === leave) workspaceLeases.delete(key);
340
340
  }
341
341
 
342
- /** True once `sessionId` closed in this runtime. */
343
- export function isShellOwnerSessionClosed(sessionId) {
344
- return closedOwnerSessions.has(String(sessionId || '').trim());
345
- }
346
-
347
342
  /** Flag a still-open job record as "termination unconfirmed" for the readouts. */
348
343
  export function markShellJobTerminationUnconfirmed(jobId) {
349
344
  return patchRunningShellJobRecord(jobId, { terminationUnconfirmed: true });
350
345
  }
351
346
 
352
- /** Record the pids a held job's termination watch last found alive, so a
353
- * reader in another process can tell a live hold from a stale one. */
354
- export function noteShellJobSurvivors(jobId, pids) {
355
- const survivorPids = (pids || []).map(Number).filter((pid) => Number.isInteger(pid) && pid > 0);
356
- return patchRunningShellJobRecord(jobId, { survivorPids });
347
+ /** Keep the bot-chain hop of the turn that launched a running job on its record,
348
+ * so a completion recovered after a restart returns at it (bot-hops.mjs). */
349
+ export function recordShellJobHop(jobId, hop) {
350
+ return patchRunningShellJobRecord(jobId, { hop });
357
351
  }
358
352
 
359
353
  function patchRunningShellJobRecord(jobId, patch) {
@@ -431,7 +425,11 @@ export async function readShellJobRecord(jobId, { strict = false } = {}) {
431
425
  const dir = jobsDir();
432
426
  const jsonPath = join(dir, `${key}.json`);
433
427
  try {
434
- const record = JSON.parse(await readFile(jsonPath, 'utf8'));
428
+ const text = await readFile(jsonPath, 'utf8');
429
+ // A crash left no data (fsync is off for these records): nothing to inspect,
430
+ // the same as no record, even for a strict reader.
431
+ if (holdsNoData(text)) return null;
432
+ const record = JSON.parse(text);
435
433
  if (
436
434
  strict &&
437
435
  (!record ||
@@ -1,11 +1,14 @@
1
1
  // Deterministic PDF fixtures shared by the read and MCP media tests.
2
- export function makePdf(pageCount) {
2
+ // `pageText(index)` is the text of page index+1; null draws a filled box and
3
+ // no text, like a scanned page without a text layer.
4
+ export function makePdf(pageCount, { pageText = (i) => `Page ${i + 1}` } = {}) {
3
5
  const objs = ['<< /Type /Catalog /Pages 2 0 R >>'];
4
6
  const kids = Array.from({ length: pageCount }, (_, i) => `${4 + i * 2} 0 R`);
5
7
  objs.push(`<< /Type /Pages /Kids [${kids.join(' ')}] /Count ${pageCount} >>`);
6
8
  objs.push('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>');
7
9
  for (let i = 0; i < pageCount; i += 1) {
8
- const stream = `BT /F1 12 Tf 10 10 Td (Page ${i + 1}) Tj ET`;
10
+ const text = pageText(i);
11
+ const stream = text === null ? '0.2 g 20 20 160 160 re f' : `BT /F1 12 Tf 10 10 Td (${text}) Tj ET`;
9
12
  objs.push(
10
13
  `<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Contents ${5 + i * 2} 0 R /Resources << /Font << /F1 3 0 R >> >> >>`
11
14
  );
@@ -44,4 +47,3 @@ export function makeEncryptedPdf() {
44
47
  out += `trailer\n<< /Size 5 /Root 1 0 R /Encrypt 4 0 R /ID [<${'cd'.repeat(16)}> <${'cd'.repeat(16)}>] >>\nstartxref\n${xref}\n%%EOF\n`;
45
48
  return Buffer.from(out, 'latin1');
46
49
  }
47
-
@@ -37,8 +37,10 @@ export function imageMimeForPath(p) {
37
37
  // behaviour (refuse over-cap originals with an isError text result, else emit
38
38
  // the raw base64 image block). Decoder rejection is an explicit tool error;
39
39
  // corrupt bytes must never enter model history.
40
- export async function readImageAsContent(fullPath, displayPath, preflightStat = null) {
41
- const mimeType = imageMimeForPath(fullPath);
40
+ // `knownMimeType` names the image type of a path whose extension does not
41
+ // (a stored attachment, recognized by its signature).
42
+ export async function readImageAsContent(fullPath, displayPath, preflightStat = null, knownMimeType = null) {
43
+ const mimeType = knownMimeType || imageMimeForPath(fullPath);
42
44
  if (!mimeType) return null;
43
45
  let st;
44
46
  try {