mixdog 1.2.2 → 1.2.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 (415) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/provider-pingpong-live.mjs +509 -0
  4. package/src/agents/advisor/AGENT.md +1 -1
  5. package/src/agents/advisor/agent.json +1 -1
  6. package/src/defaults/shipped-history.json +5 -0
  7. package/src/defaults/skills/setup/SKILL.md +6 -1
  8. package/src/defaults/skills/setup/references/actions.md +44 -22
  9. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  10. package/src/headless-exec.mjs +15 -8
  11. package/src/lib/keychain-cjs.cjs +7 -4
  12. package/src/output-styles/common.md +11 -10
  13. package/src/rules/agent/AGENT.md +5 -5
  14. package/src/rules/lead/LEAD.md +5 -5
  15. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  16. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  17. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  18. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  19. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  21. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  22. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  23. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +2 -0
  24. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +24 -17
  25. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +7 -6
  26. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +29 -10
  28. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  30. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  31. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  32. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  34. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  37. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -0
  38. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +58 -7
  39. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +29 -10
  40. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +16 -3
  41. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  42. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +12 -6
  43. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +114 -72
  45. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +37 -5
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +1 -0
  49. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  50. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +3 -0
  51. package/src/runtime/agent/orchestrator/providers/cursor.mjs +9 -2
  52. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  53. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  54. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  55. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  56. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  57. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -1
  58. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  59. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +25 -8
  60. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +87 -5
  61. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  62. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +13 -13
  63. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +355 -37
  64. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -2
  65. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +93 -47
  66. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +55 -10
  67. package/src/runtime/agent/orchestrator/providers/gemini.mjs +26 -8
  68. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +1 -0
  70. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  71. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  72. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  73. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  74. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  75. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  76. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  77. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  78. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  80. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  81. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  82. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  83. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  84. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  85. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  86. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +121 -61
  89. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  90. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +5 -2
  91. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  92. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  93. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +1 -1
  94. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  95. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  96. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  97. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  98. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +42 -24
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +63 -25
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +53 -5
  103. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -1
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +103 -26
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +23 -4
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +11 -6
  108. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  109. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +15 -23
  110. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  111. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +25 -4
  112. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  113. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  114. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +4 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +5 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  117. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +3 -1
  119. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +7 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +7 -1
  121. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +71 -24
  122. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  123. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  124. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  125. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  126. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  127. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  129. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  130. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +11 -3
  131. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  132. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  133. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  134. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +147 -8
  135. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  136. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +5 -0
  137. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  138. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  139. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  140. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  141. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  142. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  143. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  145. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  147. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  148. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  150. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  151. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  152. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  154. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  155. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
  156. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  157. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  158. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  159. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  160. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  161. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  162. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  163. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  164. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  165. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  167. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  168. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  169. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  170. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  171. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  172. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +63 -20
  173. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +2 -1
  174. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  175. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  179. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +4 -1
  180. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  181. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +16 -0
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  184. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  185. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  186. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  187. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  188. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +15 -3
  189. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  190. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  192. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  193. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  195. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  196. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +13 -0
  197. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  198. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  199. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  200. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  201. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  202. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  203. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  204. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  205. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  206. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  207. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  208. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +8 -0
  209. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +10 -1
  210. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  211. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  212. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  213. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  214. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  215. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  216. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  217. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  218. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  219. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  223. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  224. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  225. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  226. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  227. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  228. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  229. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  230. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  231. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  232. package/src/runtime/github/tool.mjs +92 -2
  233. package/src/runtime/memory/index.mjs +1 -0
  234. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  235. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  236. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  237. package/src/runtime/memory/lib/pg/process.mjs +31 -26
  238. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  239. package/src/runtime/memory/lib/pg/supervisor.mjs +108 -3
  240. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  241. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  242. package/src/runtime/office/README.md +1 -1
  243. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  244. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  245. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  246. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  247. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  248. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  249. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  250. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  251. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  252. package/src/runtime/office/core/office-sessions.mjs +5 -0
  253. package/src/runtime/office/core/office-transactions.mjs +3 -0
  254. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  255. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  256. package/src/runtime/office/index.mjs +30 -0
  257. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  258. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  259. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  260. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  261. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  262. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  263. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  264. package/src/runtime/shared/buffered-appender.mjs +18 -0
  265. package/src/runtime/shared/config.mjs +78 -18
  266. package/src/runtime/shared/err-text.mjs +6 -0
  267. package/src/runtime/shared/file-permissions.d.mts +6 -1
  268. package/src/runtime/shared/file-permissions.mjs +54 -6
  269. package/src/runtime/shared/llm/anthropic-effort.mjs +6 -0
  270. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  271. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  272. package/src/runtime/shared/loopback-listener.mjs +9 -0
  273. package/src/runtime/shared/provider-api-key.mjs +2 -0
  274. package/src/runtime/shared/review-diff.mjs +52 -0
  275. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  276. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  277. package/src/runtime/shared/slow-span.mjs +114 -0
  278. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  279. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  280. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  281. package/src/runtime/shared/tool-card-model.mjs +5 -2
  282. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  283. package/src/runtime/shared/tool-primitives.mjs +13 -0
  284. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  285. package/src/runtime/shared/tool-surface.mjs +18 -3
  286. package/src/runtime/shared/tool-work-units.mjs +40 -4
  287. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  288. package/src/runtime/shared/transcript-writer.mjs +10 -2
  289. package/src/runtime/shared/turn-changes.mjs +268 -100
  290. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  291. package/src/runtime/shared/turn-snapshot-store.mjs +19 -108
  292. package/src/runtime/shared/turn-snapshot.mjs +857 -683
  293. package/src/runtime/shared/turn-worktree-snapshot.mjs +183 -255
  294. package/src/runtime/shared/user-data-guard.mjs +262 -49
  295. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  296. package/src/runtime/shared/worktrees/contract.mjs +34 -0
  297. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  298. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  299. package/src/runtime/shared/worktrees/git.mjs +8 -3
  300. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  301. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  302. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  303. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  304. package/src/runtime/terminal-bridge/client.mjs +44 -11
  305. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  306. package/src/runtime/tidy/apply.mjs +47 -2
  307. package/src/runtime/tidy/run-engines.mjs +11 -5
  308. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  309. package/src/session-runtime/boot/apis.mjs +13 -5
  310. package/src/session-runtime/boot/facade.mjs +1 -0
  311. package/src/session-runtime/boot/infrastructure.mjs +6 -1
  312. package/src/session-runtime/boot/session.mjs +0 -1
  313. package/src/session-runtime/boot/tools.mjs +4 -1
  314. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  315. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  316. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  317. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  318. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  319. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  320. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  321. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  322. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -3
  323. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  324. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  325. package/src/session-runtime/lifecycle/teardown.mjs +72 -7
  326. package/src/session-runtime/lifecycle-api.mjs +1 -0
  327. package/src/session-runtime/mcp-glue/connect.mjs +17 -5
  328. package/src/session-runtime/model-route/set-route.mjs +2 -2
  329. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  330. package/src/session-runtime/native-web-search.mjs +10 -1
  331. package/src/session-runtime/new-session-config.mjs +4 -2
  332. package/src/session-runtime/provider-auth/login.mjs +4 -0
  333. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  334. package/src/session-runtime/runtime-review-api.mjs +8 -2
  335. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  336. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  337. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  338. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  339. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  340. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  341. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  342. package/src/session-runtime/services/agent-tool.mjs +3 -0
  343. package/src/session-runtime/services/channel-admin.mjs +19 -3
  344. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  345. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  346. package/src/session-runtime/services/provider-admin.mjs +81 -15
  347. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  348. package/src/session-runtime/session-turn-api.mjs +14 -5
  349. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  350. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  351. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  352. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  353. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  354. package/src/session-runtime/skills-api.mjs +3 -5
  355. package/src/session-runtime/tool-surface.mjs +5 -1
  356. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  357. package/src/session-runtime/turn/session-ops.mjs +7 -5
  358. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  359. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  360. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  361. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  362. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  363. package/src/session-runtime/turn/turn-run.mjs +25 -9
  364. package/src/session-runtime/usage-stats-api.mjs +43 -11
  365. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  366. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  367. package/src/session-runtime/workspace/transitions.mjs +190 -15
  368. package/src/standalone/daemon-agent-control.mjs +9 -1
  369. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  370. package/src/standalone/daemon-owner.mjs +17 -4
  371. package/src/standalone/daemon-pg-stop.mjs +51 -0
  372. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  373. package/src/standalone/daemon-stop-budget.mjs +18 -0
  374. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  375. package/src/standalone/daemon-telemetry.mjs +19 -0
  376. package/src/standalone/daemon.mjs +61 -1
  377. package/src/standalone/desktop-service-registry.mjs +5 -0
  378. package/src/standalone/local-session-runtime.mjs +70 -19
  379. package/src/standalone/session-client.mjs +42 -0
  380. package/src/standalone/session-protocol.mjs +7 -0
  381. package/src/standalone/session-proxy/projection.mjs +41 -4
  382. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  383. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  384. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  385. package/src/standalone/session-service-api.mjs +6 -1
  386. package/src/standalone/session-state-patch.d.mts +2 -1
  387. package/src/standalone/session-state-patch.mjs +41 -1
  388. package/src/tui/App.jsx +25 -0
  389. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  390. package/src/tui/app/btw-controller.mjs +118 -0
  391. package/src/tui/app/create-app-pickers.mjs +2 -0
  392. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  393. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  394. package/src/tui/app/slash-commands.mjs +13 -3
  395. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  396. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  397. package/src/tui/app/use-global-key-input.mjs +5 -12
  398. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  399. package/src/tui/components/BtwPanel.jsx +53 -0
  400. package/src/tui/components/QuestionPanel.jsx +275 -0
  401. package/src/tui/components/question-panel-model.mjs +156 -0
  402. package/src/tui/dist/index.mjs +2413 -1465
  403. package/src/tui/session/context-state.mjs +4 -2
  404. package/src/tui/session/session-api/commands.mjs +10 -0
  405. package/src/tui/session/session-api/controls.mjs +2 -2
  406. package/src/tui/session/session-api/integrations.mjs +9 -2
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  408. package/src/tui/session/session-api/lifecycle.mjs +2 -1
  409. package/src/tui/session/session-api/settings.mjs +6 -0
  410. package/src/tui/session/session-api.mjs +1 -0
  411. package/src/tui/session/tool-approval.mjs +10 -7
  412. package/src/tui/session/transcript-spill.mjs +14 -7
  413. package/src/tui/session/turn-stream.mjs +174 -31
  414. package/src/tui/session/turn.mjs +12 -5
  415. package/src/workflows/default/WORKFLOW.md +3 -1
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Spawn-free read of a checkout's HEAD: the repository layout (work tree root,
3
+ * git dir, common dir) is found by walking up from a folder to its `.git`
4
+ * (a directory, or a `gitdir:` file for linked worktrees and submodules), and
5
+ * HEAD is the one small file in the git dir. Packed refs never matter: HEAD
6
+ * names a branch (`ref: refs/heads/x`) or holds a commit id.
7
+ */
8
+ import { readFileSync, statSync, watch } from 'node:fs';
9
+ import { dirname, resolve } from 'node:path';
10
+ import { canonicalPath } from './paths.mjs';
11
+
12
+ const OBJECT_ID = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/i;
13
+ const NOT_A_REPO_TTL_MS = 1000;
14
+
15
+ /** start dir → { topLevel, gitDir, commonDir } for folders inside a checkout. */
16
+ const layouts = new Map();
17
+ /** start dir → time a walk found no repository. */
18
+ const notRepos = new Map();
19
+ /** git dir → last HEAD read, kept across a half-written HEAD. */
20
+ const lastHeads = new Map();
21
+
22
+ function readText(path) {
23
+ try {
24
+ return readFileSync(path, 'utf8');
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ function isDirectory(path) {
31
+ try {
32
+ return statSync(path).isDirectory();
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ function isFile(path) {
39
+ try {
40
+ return statSync(path).isFile();
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ function layoutAbove(start) {
47
+ for (let dir = start; ; dir = dirname(dir)) {
48
+ const dotGit = resolve(dir, '.git');
49
+ let gitDir = null;
50
+ if (isDirectory(dotGit)) {
51
+ gitDir = dotGit;
52
+ } else if (isFile(dotGit)) {
53
+ const pointer = /^gitdir:\s*(.+?)\s*$/m.exec(readText(dotGit) ?? '');
54
+ if (pointer) gitDir = resolve(dir, pointer[1]);
55
+ }
56
+ if (gitDir && isFile(resolve(gitDir, 'HEAD'))) {
57
+ const common = readText(resolve(gitDir, 'commondir'))?.trim();
58
+ return {
59
+ topLevel: canonicalPath(dir),
60
+ gitDir,
61
+ commonDir: canonicalPath(common ? resolve(gitDir, common) : gitDir),
62
+ };
63
+ }
64
+ if (dirname(dir) === dir) return null;
65
+ }
66
+ }
67
+
68
+ /** The checkout layout containing `cwd`, or null outside a repository. */
69
+ export function findGitLayout(cwd) {
70
+ const start = resolve(String(cwd || '.'));
71
+ const known = layouts.get(start);
72
+ if (known && isFile(resolve(known.gitDir, 'HEAD'))) return known;
73
+ layouts.delete(start);
74
+ const missedAt = notRepos.get(start);
75
+ if (missedAt !== undefined && Date.now() - missedAt < NOT_A_REPO_TTL_MS) return null;
76
+ const found = layoutAbove(start);
77
+ if (!found) {
78
+ notRepos.set(start, Date.now());
79
+ return null;
80
+ }
81
+ notRepos.delete(start);
82
+ layouts.set(start, found);
83
+ return found;
84
+ }
85
+
86
+ const MAX_SYMREF_DEPTH = 5;
87
+ const PER_WORKTREE_REFS = /^refs\/(?:bisect|worktree|rewritten)\//;
88
+
89
+ /** Where a ref's loose file lives: per-worktree refs in the git dir, the rest in the common dir. */
90
+ function refFile(layout, name) {
91
+ return resolve(PER_WORKTREE_REFS.test(name) ? layout.gitDir : layout.commonDir, name);
92
+ }
93
+
94
+ /**
95
+ * Follows HEAD's symbolic refs like git does (packed-refs never holds a
96
+ * symref, so only loose ref files are read). Returns `{ name, files }`: the
97
+ * ref HEAD finally resolves to (the first non-symbolic ref, or the first
98
+ * missing target: an unborn branch) and every file read along the chain, or
99
+ * `{ name: null, files }` for a cycle or a chain over the depth limit.
100
+ */
101
+ function followHead(layout, text) {
102
+ const files = [resolve(layout.gitDir, 'HEAD')];
103
+ let name = /^ref:\s*(.+)$/.exec(text)?.[1].trim();
104
+ const seen = new Set();
105
+ for (let depth = 0; name && depth < MAX_SYMREF_DEPTH; depth++) {
106
+ if (seen.has(name)) return { name: null, files };
107
+ seen.add(name);
108
+ const file = refFile(layout, name);
109
+ files.push(file);
110
+ const next = /^ref:\s*(.+)$/.exec(readText(file)?.trim() ?? '')?.[1].trim();
111
+ if (!next) return { name, files };
112
+ name = next;
113
+ }
114
+ return { name: null, files };
115
+ }
116
+
117
+ /** Files whose change can change what `gitDir`'s HEAD resolves to. */
118
+ function headChainFiles(layout) {
119
+ const text = readText(resolve(layout.gitDir, 'HEAD'))?.trim();
120
+ return text && /^ref:/.test(text) ? followHead(layout, text).files : [resolve(layout.gitDir, 'HEAD')];
121
+ }
122
+
123
+ /**
124
+ * What `cwd`'s checkout has checked out: `{ topLevel, gitDir, repoCommonDir,
125
+ * branch, commit }` (`branch` null when detached or when the symbolic-ref
126
+ * chain is broken, `commit` null on a branch), or null when `cwd` is not
127
+ * inside a git repository. A branch alias resolves to the branch it points at.
128
+ */
129
+ export function readGitHead(cwd) {
130
+ const layout = findGitLayout(cwd);
131
+ if (!layout) return null;
132
+ const text = readText(resolve(layout.gitDir, 'HEAD'))?.trim();
133
+ let head = lastHeads.get(layout.gitDir) ?? { branch: null, commit: null };
134
+ if (text && /^ref:\s*(.+)$/.test(text)) {
135
+ const { name } = followHead(layout, text);
136
+ head = { branch: name?.startsWith('refs/heads/') ? name.slice('refs/heads/'.length) : null, commit: null };
137
+ } else if (text && OBJECT_ID.test(text)) {
138
+ head = { branch: null, commit: text.toLowerCase() };
139
+ }
140
+ lastHeads.set(layout.gitDir, head);
141
+ return {
142
+ topLevel: layout.topLevel,
143
+ gitDir: layout.gitDir,
144
+ repoCommonDir: layout.commonDir,
145
+ branch: head.branch,
146
+ commit: head.commit,
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Calls `onChange` shortly after the HEAD of the checkout `getCwd()` names
152
+ * changes (a `git switch` from any terminal). Call `retarget()` whenever the
153
+ * folder may have changed; `dispose()` ends the watch.
154
+ */
155
+ export function createGitHeadWatcher({ getCwd, onChange, debounceMs = 40 }) {
156
+ let target = '';
157
+ let watchers = [];
158
+ let timer = null;
159
+ const closeWatchers = () => {
160
+ for (const watcher of watchers) watcher.close();
161
+ watchers = [];
162
+ };
163
+ const notify = () => {
164
+ clearTimeout(timer);
165
+ timer = setTimeout(onChange, debounceMs);
166
+ timer.unref?.();
167
+ };
168
+ const retarget = () => {
169
+ const layout = findGitLayout(getCwd());
170
+ // HEAD and every symbolic ref it passes through: a change anywhere in
171
+ // that chain can change the branch.
172
+ const files = layout ? headChainFiles(layout) : [];
173
+ const key = files.join('\n');
174
+ if (key === target) return;
175
+ closeWatchers();
176
+ target = key;
177
+ // The directories, not the files: git replaces a ref by renaming <ref>.lock.
178
+ const dirs = [...new Set(files.map((file) => dirname(file)))];
179
+ for (const dir of dirs) {
180
+ try {
181
+ const watcher = watch(dir, { persistent: false }, (_event, name) => {
182
+ if (name && !files.includes(resolve(dir, String(name)))) return;
183
+ retarget();
184
+ notify();
185
+ });
186
+ watcher.on('error', () => {
187
+ watcher.close();
188
+ watchers = watchers.filter((w) => w !== watcher);
189
+ target = '';
190
+ });
191
+ watchers.push(watcher);
192
+ } catch {
193
+ // A chain directory that does not exist (an unborn target) is not watched.
194
+ }
195
+ }
196
+ };
197
+ return {
198
+ retarget,
199
+ dispose() {
200
+ clearTimeout(timer);
201
+ closeWatchers();
202
+ target = '';
203
+ },
204
+ };
205
+ }
@@ -68,14 +68,19 @@ const READ_ONLY_FLAGS = ['--no-optional-locks', '-c', 'core.fsmonitor=false'];
68
68
  /**
69
69
  * @param {string} cwd
70
70
  * @param {string[]} args
71
- * @param {{ signal?: AbortSignal, timeoutMs?: number, maxBytes?: number, allowFailure?: boolean, readOnly?: boolean }} [options]
71
+ * @param {{ signal?: AbortSignal, timeoutMs?: number, maxBytes?: number, allowFailure?: boolean, readOnly?: boolean, env?: Record<string, string> }} [options]
72
+ * `env`: variables set on top of the pinned environment (a temporary GIT_INDEX_FILE, a commit identity).
72
73
  * @returns {Promise<{ code: number, stdout: string, stderr: string }>}
73
74
  */
74
- export async function runGit(cwd, args, { signal, timeoutMs, maxBytes, allowFailure = false, readOnly = false } = {}) {
75
+ export async function runGit(
76
+ cwd,
77
+ args,
78
+ { signal, timeoutMs, maxBytes, allowFailure = false, readOnly = false, env = null } = {}
79
+ ) {
75
80
  throwIfAborted(signal);
76
81
  const result = await runGitOffThread([...(readOnly ? READ_ONLY_FLAGS : []), ...SCOPED_CONFIG, ...args], {
77
82
  cwd,
78
- env: gitEnvironment(),
83
+ env: env ? { ...gitEnvironment(), ...env } : gitEnvironment(),
79
84
  timeoutMs: timeoutMs ?? DEFAULT_TIMEOUT_MS,
80
85
  maxBytes: maxBytes ?? DEFAULT_MAX_BYTES,
81
86
  });
@@ -88,7 +88,7 @@ export async function cleanupDeletedSessionWorktree(sessionId, { manager = null,
88
88
  try {
89
89
  return { entryId: entry.id, ...(await backend.removeManagedWorktree({ entryId: entry.id })) };
90
90
  } catch (error) {
91
- if (isWorktreeError(error, 'WORKTREE_DIRTY') || isWorktreeError(error, 'WORKTREE_UNMERGED')) {
91
+ if (isWorktreeError(error, 'WORKTREE_DIRTY') || isWorktreeError(error, 'WORKTREE_UNMERGED') || isWorktreeError(error, 'WORKTREE_FILE_RECOVERY')) {
92
92
  return { entryId: entry.id, removed: false, reason: error.code };
93
93
  }
94
94
  throw error;
@@ -26,8 +26,9 @@
26
26
  * -d` forgets it).
27
27
  */
28
28
  import { existsSync } from 'node:fs';
29
+ import { assertRecoveryStoresClear, recoveryScopes, retainedRecoveryScopes, validateRecoveryScope } from './file-recovery.mjs';
29
30
  import { lstat, mkdir, readdir, readFile, rm, rmdir } from 'node:fs/promises';
30
- import { dirname, join, relative, sep } from 'node:path';
31
+ import { dirname, join, relative, resolve, sep } from 'node:path';
31
32
  import {
32
33
  managedWorktreePath,
33
34
  managedWorktreeRoot,
@@ -37,6 +38,13 @@ import {
37
38
  worktreeBranchName,
38
39
  worktreeError,
39
40
  } from './contract.mjs';
41
+ import {
42
+ assertCarryable,
43
+ carryUncommittedChanges,
44
+ dropRecoveryRef,
45
+ restoreFromRecoveryRef,
46
+ uncommittedChanges,
47
+ } from './carry.mjs';
40
48
  import { freeBytesAt, requiredBytes, trackedCheckoutBytes } from './disk.mjs';
41
49
  import {
42
50
  branchCommit,
@@ -64,7 +72,7 @@ import { withRepoLock as withRepoMutationLock } from './lock.mjs';
64
72
  import { mergeBranchIntoCheckout } from './merge.mjs';
65
73
  import { readOwnerLease, removeOwnerLease, sameOwner, writeOwnerLease } from './owner.mjs';
66
74
  import { canonicalPath, checkoutFolder, isWithin, pathKey, projectSubdirOf, samePath } from './paths.mjs';
67
- import { deleteRecord, entryView, getRecord, insertRecord, putRecord, readRegistry } from './registry.mjs';
75
+ import { deleteRecord, entryView, getRecord, insertRecord, putRecord, readRegistry, registryPath } from './registry.mjs';
68
76
  import { normalizeWorktreeSettings } from './settings.mjs';
69
77
  import { slugFromHint, uniqueSlug } from './slug.mjs';
70
78
 
@@ -74,6 +82,8 @@ const REMOVE_RETRY_DELAY_MS = 150;
74
82
  // The session copies the project identity into each managed worktree; it is
75
83
  // Mixdog metadata, not user data, so it alone never makes a worktree dirty.
76
84
  const SESSION_PROJECT_ID_PATH = '.mixdog/project.id';
85
+ // Changed paths WORKTREE_DIRTY_CHOICE lists for the dialog's preview (the count is exact).
86
+ const DIRTY_CHOICE_PREVIEW = 50;
77
87
 
78
88
  export { registerRootReleaser } from './contract.mjs';
79
89
 
@@ -327,6 +337,7 @@ async function deleteBranchIfFree(record) {
327
337
  * still present (empty when everything is gone).
328
338
  */
329
339
  async function removeCheckout(record) {
340
+ await assertRecoveryStoresClear(record);
330
341
  await assertManagedCheckoutPath(record);
331
342
  const remaining = [];
332
343
  for (const path of await removeIncludeLinks(record.resources?.includes)) remaining.push({ kind: 'link', path });
@@ -437,6 +448,7 @@ async function rollbackCreation(record, cause) {
437
448
  * and retried with the deletion; it never keeps the checkout.
438
449
  */
439
450
  async function finishDeletion(record) {
451
+ await assertRecoveryStoresClear(record);
440
452
  const remaining = [];
441
453
  try {
442
454
  await removeOwnerLease(record);
@@ -516,6 +528,46 @@ export async function findWorktreeContaining(path) {
516
528
  return record ? entryView(record) : null;
517
529
  }
518
530
 
531
+ /** File-host seam, not a user capability. Persist the host-owned scope before
532
+ * preparing a byte journal, and hold the same repository lock as every removal
533
+ * through the operation. The descriptor survives daemon exit/startup reconcile.
534
+ * @template T
535
+ * @param {string} root
536
+ * @param {string} directory
537
+ * @param {'write'|'restore'|'inspect'|'discard'} action
538
+ * @param {() => Promise<T>} run
539
+ * @returns {Promise<T>}
540
+ */
541
+ export async function withWorktreeRecoveryStore(root, directory, action, run) {
542
+ const entry = await findWorktreeContaining(root);
543
+ if (!entry) {
544
+ // A managed checkout can disappear between host authorization and this
545
+ // lookup. Its reserved namespace must never become an ordinary project.
546
+ // Retain a lexical check as well, so a replacement link cannot escape it.
547
+ const store = dirname(registryPath());
548
+ const lexicalKey = (path) => process.platform === 'win32' ? resolve(path).toLowerCase() : resolve(path);
549
+ const requested = lexicalKey(root);
550
+ if (isWithin(store, root) || [store, canonicalPath(store)].some((path) =>
551
+ requested === lexicalKey(path) || requested.startsWith(lexicalKey(path) + sep)))
552
+ throw worktreeError('WORKTREE_MISSING', undefined, { path: root });
553
+ return run();
554
+ }
555
+ return withRepoLock(entry.repoCommonDir, async () => {
556
+ const record = await getRecord(entry.id);
557
+ if (!record || ((action === 'write' || action === 'restore') && record.state !== 'ready'))
558
+ throw worktreeError('WORKTREE_MISSING', undefined, { entryId: entry.id });
559
+ const scope = validateRecoveryScope(record, { root, directory });
560
+ let scopes = recoveryScopes(record);
561
+ if (!scopes.some((item) => item.root === root && item.directory === directory)) {
562
+ if (scopes.length >= 64) scopes = await retainedRecoveryScopes(record);
563
+ if (scopes.length >= 64) throw worktreeError('WORKTREE_FILE_RECOVERY');
564
+ record.editorRecoveryStores = [...scopes, scope];
565
+ await putRecord(record);
566
+ }
567
+ return run();
568
+ });
569
+ }
570
+
519
571
  /** Mark an entry disposable: startup reconcile may remove it once clean and merged. */
520
572
  export async function markWorktreeDisposable(entryId, disposable = true) {
521
573
  const record = await getRecord(entryId);
@@ -600,8 +652,20 @@ async function diskFullFailure(error, { needed, root }) {
600
652
  }
601
653
 
602
654
  /**
655
+ * `carryChanges` decides the project folder's uncommitted changes (tracked
656
+ * and untracked, ignored excluded), judged under the repository lock:
657
+ * omitted/null leaves them unexamined; `ask` refuses with
658
+ * WORKTREE_DIRTY_CHOICE (`details: { count, paths }`, paths capped) while
659
+ * there are any; `leave` keeps them in the project folder; `carry` moves
660
+ * them into the new worktree (carry.mjs: whole checkout, ignored files stay)
661
+ * — refused (WORKTREE_DIRTY) during a merge, rebase or cherry-pick, with
662
+ * unmerged entries, dirty submodules, filtered (LFS) paths or oversized
663
+ * untracked files. The result's `changes` (`{ mode: 'carried'|'left',
664
+ * count, recoveryRef? }`) reports what happened to them; a carried move's
665
+ * `recoveryRef` stays until dropCarryRecoveryRef / restoreCarriedChanges.
603
666
  * @param {{ projectPath: string, sessionId: string, runtimeToken: string, baseBranch?: string|null,
604
- * nameHint?: string, confirmLarge?: boolean, txId: string, signal?: AbortSignal }} input
667
+ * nameHint?: string, confirmLarge?: boolean, txId: string, signal?: AbortSignal,
668
+ * carryChanges?: 'ask'|'leave'|'carry'|null }} input
605
669
  */
606
670
  export async function createManagedWorktree({
607
671
  projectPath,
@@ -612,9 +676,13 @@ export async function createManagedWorktree({
612
676
  confirmLarge = false,
613
677
  txId,
614
678
  signal,
679
+ carryChanges = null,
615
680
  } = {}) {
616
681
  requireText(projectPath, 'projectPath');
617
682
  requireText(txId, 'txId');
683
+ if (![null, 'ask', 'leave', 'carry'].includes(carryChanges)) {
684
+ throw new TypeError('carryChanges must be ask, leave or carry.');
685
+ }
618
686
  const owner = normalizeWorktreeOwner({ sessionId, runtimeToken, pid: process.pid });
619
687
  throwIfAborted(signal);
620
688
  const repo = await inspectRepo(projectPath, { signal });
@@ -623,6 +691,18 @@ export async function createManagedWorktree({
623
691
  if (existing) return existing;
624
692
  throwIfAborted(signal);
625
693
 
694
+ const projectSubdir = projectSubdirOf(repo.topLevel, repo.projectPath);
695
+ // A carry is judged by the same status read that counts the changes.
696
+ let pendingChanges = [];
697
+ if (carryChanges === 'carry') pendingChanges = await assertCarryable(repo.topLevel, projectSubdir);
698
+ else if (carryChanges) pendingChanges = await uncommittedChanges(repo.topLevel, projectSubdir);
699
+ if (pendingChanges.length && carryChanges === 'ask') {
700
+ throw worktreeError('WORKTREE_DIRTY_CHOICE', undefined, {
701
+ count: pendingChanges.length,
702
+ paths: pendingChanges.slice(0, DIRTY_CHOICE_PREVIEW),
703
+ });
704
+ }
705
+
626
706
  const settings = await currentWorktreeSettings();
627
707
  let baseCommit = repo.headCommit;
628
708
  let mergeTarget = repo.currentBranch;
@@ -675,6 +755,7 @@ export async function createManagedWorktree({
675
755
  timeoutMs: WORKTREE_ADD_TIMEOUT_MS,
676
756
  });
677
757
  let record = await reserve();
758
+ let carried = null;
678
759
 
679
760
  try {
680
761
  await mkdir(root, { recursive: true });
@@ -708,16 +789,46 @@ export async function createManagedWorktree({
708
789
  await setResource(record, 'owner', 'pending');
709
790
  await writeOwnerLease(record, owner);
710
791
  await setResource(record, 'owner', 'created');
792
+ // Carried before the include entries are copied, so an include never
793
+ // stands in the way of a carried file; the status is read again here,
794
+ // right before acting.
795
+ let changes = pendingChanges.length ? { mode: 'left', count: pendingChanges.length } : null;
796
+ if (carryChanges === 'carry') {
797
+ carried = await carryUncommittedChanges({
798
+ topLevel: repo.topLevel,
799
+ projectSubdir,
800
+ worktreePath: record.path,
801
+ baseCommit,
802
+ label: record.id,
803
+ });
804
+ // The recovery ref stays until the caller has committed the move
805
+ // (dropCarryRecoveryRef) or put the changes back (restoreCarriedChanges).
806
+ changes = carried.count ? { mode: 'carried', count: carried.count, recoveryRef: carried.recoveryRef } : null;
807
+ }
711
808
  const applied = await applyIncludes(includePlan, record.path, (ledger) => recordInclude(record, ledger));
712
809
  throwIfAborted(signal);
713
810
  record.warnings = [...record.warnings, ...applied.warnings];
714
811
  record.state = 'ready';
715
812
  await putRecord(record);
716
- return entryView(record);
813
+ return { ...entryView(record), ...(changes ? { changes } : {}) };
717
814
  } catch (error) {
815
+ // Carried already: the changes go back to the project folder before the
816
+ // worktree that holds them is rolled back (the recovery ref keeps them
817
+ // either way).
818
+ let carryRestore = null;
819
+ if (carried?.recoveryRef) {
820
+ carryRestore = await restoreFromRecoveryRef(repo.topLevel, projectSubdir, carried.recoveryRef)
821
+ .then(() => ({ restored: true, recoveryRef: carried.recoveryRef }))
822
+ .catch((restoreError) => ({
823
+ restored: false,
824
+ recoveryRef: carried.recoveryRef,
825
+ error: String(restoreError?.message || restoreError),
826
+ }));
827
+ }
718
828
  const rollback = await rollbackCreation(record, error);
719
829
  const failure = isDiskFullError(error) ? await diskFullFailure(error, { needed, root }) : error;
720
830
  if (!rollback.complete) failure.rollback = rollback;
831
+ if (carryRestore) failure.carryRestore = carryRestore;
721
832
  throw failure;
722
833
  }
723
834
 
@@ -758,6 +869,30 @@ export async function createManagedWorktree({
758
869
  });
759
870
  }
760
871
 
872
+ /**
873
+ * After a carry whose session move failed: put the changes kept at
874
+ * `recoveryRef` back into the project folder (refused, ref kept, when the
875
+ * folder changed meanwhile). The ref stays; drop it with dropCarryRecoveryRef.
876
+ * @param {{ projectPath: string, recoveryRef: string }} input
877
+ */
878
+ export async function restoreCarriedChanges({ projectPath, recoveryRef } = {}) {
879
+ requireText(projectPath, 'projectPath');
880
+ const repo = await inspectRepo(projectPath);
881
+ return withRepoLock(repo.repoCommonDir, () =>
882
+ restoreFromRecoveryRef(repo.topLevel, projectSubdirOf(repo.topLevel, repo.projectPath), recoveryRef)
883
+ );
884
+ }
885
+
886
+ /**
887
+ * Forget a carry's recovery ref once the session move it protected is committed.
888
+ * @param {{ projectPath: string, recoveryRef: string }} input
889
+ */
890
+ export async function dropCarryRecoveryRef({ projectPath, recoveryRef } = {}) {
891
+ requireText(projectPath, 'projectPath');
892
+ const repo = await inspectRepo(projectPath);
893
+ return withRepoLock(repo.repoCommonDir, () => dropRecoveryRef(repo.topLevel, recoveryRef));
894
+ }
895
+
761
896
  /**
762
897
  * Claim the live lease of a retained worktree for one runtime of its session.
763
898
  * @param {string} entryId
@@ -1054,12 +1189,14 @@ async function assertRemovable(record) {
1054
1189
  /**
1055
1190
  * The refusal removeManagedWorktree would raise, checked before anything
1056
1191
  * moves (a session leaves its worktree only for a removal that can proceed).
1057
- * Unknown entries are WORKTREE_MISSING; `force` and non-ready entries pass.
1192
+ * Unknown entries are WORKTREE_MISSING. Recovery records block even `force`
1193
+ * and non-ready entries; ordinary dirty/unmerged checks apply only to ready ones.
1058
1194
  * @param {{ entryId: string, force?: boolean }} input
1059
1195
  */
1060
1196
  export async function assertWorktreeRemovable({ entryId, force = false } = {}) {
1061
1197
  const record = await getRecord(entryId);
1062
1198
  if (!record) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
1199
+ await assertRecoveryStoresClear(record);
1063
1200
  if (record.state !== 'ready') return;
1064
1201
  await assertManagedCheckoutPath(record);
1065
1202
  if (!force) await assertRemovable(record);
@@ -1067,7 +1204,8 @@ export async function assertWorktreeRemovable({ entryId, force = false } = {}) {
1067
1204
 
1068
1205
  /**
1069
1206
  * Remove a managed worktree. Refuses dirty (incl. untracked and modified
1070
- * include copies) or unmerged worktrees unless `force`.
1207
+ * include copies) or unmerged worktrees unless `force`. Retained file recovery
1208
+ * data blocks removal regardless of force.
1071
1209
  * @param {{ entryId: string, force?: boolean }} input
1072
1210
  * @returns {Promise<{ removed: boolean, retryable?: boolean, remaining?: object[], branchDeleted?: boolean }>}
1073
1211
  */
@@ -1082,6 +1220,7 @@ export async function removeManagedWorktree({ entryId, force = false } = {}) {
1082
1220
  return rollback.complete ? { removed: true } : { removed: false, retryable: true, remaining: rollback.remaining };
1083
1221
  }
1084
1222
  if (current.state === 'ready') {
1223
+ await assertRecoveryStoresClear(current);
1085
1224
  await assertManagedCheckoutPath(current);
1086
1225
  if (!force) await assertRemovable(current);
1087
1226
  current.state = 'deleting';
@@ -1101,7 +1240,7 @@ function leaseProcessAlive(lease) {
1101
1240
  * Every managed worktree with who keeps it: `retained` (its session is in
1102
1241
  * `retainedSessionIds`, i.e. still in the session store), `ownerLive` (a
1103
1242
  * live session or a running lease holder), and `removable` (what a prune
1104
- * would remove: neither, or disposable and not live).
1243
+ * would remove: neither, or disposable and not live, with no retained recovery data).
1105
1244
  * @param {{ liveSessionIds?: string[], retainedSessionIds?: string[] }} input
1106
1245
  */
1107
1246
  export async function listManagedWorktrees({ liveSessionIds = [], retainedSessionIds = [] } = {}) {
@@ -1112,12 +1251,20 @@ export async function listManagedWorktrees({ liveSessionIds = [], retainedSessio
1112
1251
  const ownerLive =
1113
1252
  (!!record.ownerSessionId && live.has(record.ownerSessionId)) || leaseProcessAlive(await readOwnerLease(record));
1114
1253
  const isRetained = !!record.ownerSessionId && retained.has(record.ownerSessionId);
1254
+ let removable = record.state !== 'ready' || (!ownerLive && (record.disposable === true || !isRetained));
1255
+ if (removable) {
1256
+ try { await assertRecoveryStoresClear(record); }
1257
+ catch (error) {
1258
+ if (error?.code !== 'WORKTREE_FILE_RECOVERY') throw error;
1259
+ removable = false;
1260
+ }
1261
+ }
1115
1262
  rows.push({
1116
1263
  ...entryView(record),
1117
1264
  projectPath: record.projectPath,
1118
1265
  retained: isRetained,
1119
1266
  ownerLive,
1120
- removable: record.state !== 'ready' || (!ownerLive && (record.disposable === true || !isRetained)),
1267
+ removable,
1121
1268
  });
1122
1269
  }
1123
1270
  return rows;
@@ -1194,6 +1341,7 @@ export async function reconcileManagedWorktrees({
1194
1341
  report.kept.push({ id: record.id, reason: dirty.length ? 'dirty' : 'unmerged' });
1195
1342
  continue;
1196
1343
  }
1344
+ await assertRecoveryStoresClear(record);
1197
1345
  record.state = 'deleting';
1198
1346
  record.deleting = { force: false, startedAt: Date.now(), remaining: [] };
1199
1347
  await putRecord(record);
@@ -16,6 +16,7 @@
16
16
  * non-zero exit.
17
17
  */
18
18
  import { existsSync } from 'node:fs';
19
+ import { readFile } from 'node:fs/promises';
19
20
  import { resolve } from 'node:path';
20
21
  import { worktreeError } from './contract.mjs';
21
22
  import { firstLine, parseStatusPaths } from './git.mjs';
@@ -27,32 +28,41 @@ function failure(args, result) {
27
28
  return error;
28
29
  }
29
30
 
30
- /** The sequencer operation in progress in `cwd`, or ''. */
31
+ /**
32
+ * The operation in progress in `cwd` — 'merge', 'cherry-pick', 'revert',
33
+ * 'rebase' or 'bisect' — or ''. A `sequencer/` folder left without a
34
+ * CHERRY_PICK_HEAD/REVERT_HEAD (a multi-commit pick between two commits) is
35
+ * named after the first command of its todo list.
36
+ */
31
37
  export async function operationInProgress(git, cwd) {
32
- for (const [ref, name] of [
33
- ['MERGE_HEAD', 'merge'],
34
- ['CHERRY_PICK_HEAD', 'cherry-pick'],
35
- ['REVERT_HEAD', 'revert'],
36
- ]) {
37
- if ((await git(cwd, ['rev-parse', '--verify', '--quiet', ref])).code === 0) return name;
38
- }
38
+ // One git call resolves every state marker's path; the markers are plain
39
+ // files/folders in the git dir (BISECT_START and sequencer/ are per worktree).
40
+ const operations = ['merge', 'cherry-pick', 'revert', 'rebase', 'rebase', 'bisect', 'sequencer'];
41
+ const markers = [
42
+ 'MERGE_HEAD',
43
+ 'CHERRY_PICK_HEAD',
44
+ 'REVERT_HEAD',
45
+ 'rebase-merge',
46
+ 'rebase-apply',
47
+ 'BISECT_START',
48
+ 'sequencer',
49
+ ];
39
50
  const paths = await git(cwd, [
40
51
  'rev-parse',
41
52
  '--path-format=absolute',
42
- '--git-path',
43
- 'rebase-merge',
44
- '--git-path',
45
- 'rebase-apply',
53
+ ...markers.flatMap((marker) => ['--git-path', marker]),
46
54
  ]);
47
- if (paths.code === 0) {
48
- for (const path of String(paths.stdout)
49
- .split(/\r?\n/)
50
- .map((line) => line.trim())
51
- .filter(Boolean)) {
52
- if (existsSync(resolve(cwd, path))) return 'rebase';
53
- }
54
- }
55
- return '';
55
+ if (paths.code !== 0) return '';
56
+ const found = String(paths.stdout)
57
+ .split(/\r?\n/)
58
+ .map((line) => line.trim())
59
+ .filter(Boolean);
60
+ const index = found.findIndex((path) => existsSync(resolve(cwd, path)));
61
+ if (index < 0) return '';
62
+ if (operations[index] !== 'sequencer') return operations[index];
63
+ const todo = await readFile(resolve(cwd, found[index], 'todo'), 'utf8').catch(() => '');
64
+ const first = todo.split(/\r?\n/).find((line) => line.trim() && !line.trim().startsWith('#')) || '';
65
+ return /^\s*revert\s/.test(first) ? 'revert' : 'cherry-pick';
56
66
  }
57
67
 
58
68
  /** Changed and untracked paths in `cwd` (porcelain v1, -z). */
@@ -0,0 +1,6 @@
1
+ export function canonicalPath(value: string): string;
2
+ export function pathKey(value: string, platform?: string): string;
3
+ export function samePath(left: string, right: string): boolean;
4
+ export function isWithin(parent: string, child: string): boolean;
5
+ export function projectSubdirOf(topLevel?: string | null, projectPath?: string | null): string;
6
+ export function checkoutFolder(root: string, subdir?: string): string;