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
@@ -15,23 +15,49 @@ import {
15
15
  rm as rmP,
16
16
  stat as statP,
17
17
  } from 'node:fs/promises';
18
- import { dirname, join } from 'node:path';
18
+ import { basename, dirname, join, resolve } from 'node:path';
19
19
  import { homedir } from 'node:os';
20
- import { createHash } from 'node:crypto';
20
+ import { createHash, randomUUID } from 'node:crypto';
21
21
  import { isPlainObject } from './object.mjs';
22
+ import { mixdogHome } from './plugin-paths.mjs';
22
23
 
23
- function mixdogConfigBaseDir() {
24
- return process.env.MIXDOG_CONFIG_DIR || join(homedir(), '.mixdog');
25
- }
26
-
24
+ // The root follows the isolated home (MIXDOG_HOME, then the legacy
25
+ // MIXDOG_CONFIG_DIR), so a dev profile keeps its backups inside its own
26
+ // profile; the installed app resolves to ~/.mixdog/backups/user-data.
27
27
  function getBackupRoot() {
28
- return process.env.MIXDOG_USER_DATA_BACKUP_ROOT || join(mixdogConfigBaseDir(), 'backups', 'user-data');
28
+ return (
29
+ process.env.MIXDOG_USER_DATA_BACKUP_ROOT ||
30
+ join(process.env.MIXDOG_HOME || process.env.MIXDOG_CONFIG_DIR || mixdogHome(), 'backups', 'user-data')
31
+ );
29
32
  }
30
33
 
31
34
  const USER_DATA_FILES = ['mixdog-config.json'];
32
35
 
33
36
  const USER_DATA_DIRS = ['schedules', 'webhooks', 'workflows'];
34
37
 
38
+ // Written into every backup; a backup without one (legacy, or interrupted
39
+ // mid-copy) is never restored.
40
+ const BACKUP_MANIFEST = 'backup.json';
41
+
42
+ // Lives INSIDE the data dir: deleting or recreating the profile drops it, so a
43
+ // recreated profile at the same path is fresh. Its instanceId ties backups to
44
+ // this incarnation of the data dir.
45
+ const INIT_MARKER = '.mixdog-user-data.json';
46
+
47
+ const SCOPE_PREFIX = 'scope-';
48
+
49
+ function normalizeDataDir(dataDir) {
50
+ const full = resolve(String(dataDir));
51
+ return process.platform === 'win32' ? full.toLowerCase() : full;
52
+ }
53
+
54
+ // Per-data-dir backup folder. The `scope-` prefix sorts after the timestamped
55
+ // legacy entries, so an older build pruning the root as one ring keeps it.
56
+ function scopeDirFor(dataDir) {
57
+ const id = createHash('sha256').update(normalizeDataDir(dataDir)).digest('hex').slice(0, 16);
58
+ return join(getBackupRoot(), `${SCOPE_PREFIX}${id}`);
59
+ }
60
+
35
61
  function stamp() {
36
62
  return new Date().toISOString().replace(/[:.]/g, '-');
37
63
  }
@@ -44,16 +70,105 @@ function safeReason(reason) {
44
70
  );
45
71
  }
46
72
 
47
- function initMarkerPath(dataDir) {
48
- const id = createHash('sha256')
49
- .update(String(dataDir || 'unknown'))
50
- .digest('hex')
51
- .slice(0, 16);
52
- return join(getBackupRoot(), `.initialized-${id}.json`);
73
+ /** instanceId of this data dir's init marker; null when absent or unreadable. */
74
+ function readInitInstanceId(dataDir) {
75
+ if (!dataDir) return null;
76
+ try {
77
+ const marker = JSON.parse(readFileSync(join(dataDir, INIT_MARKER), 'utf8'));
78
+ return typeof marker?.instanceId === 'string' && marker.instanceId ? marker.instanceId : null;
79
+ } catch {
80
+ return null;
81
+ }
53
82
  }
54
83
 
55
84
  export function hasUserDataInitMarker(dataDir) {
56
- return existsSync(initMarkerPath(dataDir));
85
+ return readInitInstanceId(dataDir) != null;
86
+ }
87
+
88
+ // Older builds kept one unscoped ring plus `.initialized-<hash>.json` markers
89
+ // under MIXDOG_CONFIG_DIR (or ~/.mixdog), ignoring MIXDOG_HOME.
90
+ function legacyBackupRoots() {
91
+ const legacy =
92
+ process.env.MIXDOG_USER_DATA_BACKUP_ROOT ||
93
+ join(process.env.MIXDOG_CONFIG_DIR || join(homedir(), '.mixdog'), 'backups', 'user-data');
94
+ const current = getBackupRoot();
95
+ return normalizeDataDir(legacy) === normalizeDataDir(current) ? [current] : [current, legacy];
96
+ }
97
+
98
+ function manifestDataDirMatches(backupDir, dataDirKey) {
99
+ try {
100
+ const manifest = JSON.parse(readFileSync(join(backupDir, BACKUP_MANIFEST), 'utf8'));
101
+ return typeof manifest?.dataDir === 'string' && normalizeDataDir(manifest.dataDir) === dataDirKey;
102
+ } catch {
103
+ return false;
104
+ }
105
+ }
106
+
107
+ function legacyMarkersFor(root, dataDir, dataDirKey) {
108
+ const hashed = new Set(
109
+ [String(dataDir), resolve(String(dataDir))].map(
110
+ (value) => `.initialized-${createHash('sha256').update(value).digest('hex').slice(0, 16)}.json`
111
+ )
112
+ );
113
+ let names = [];
114
+ try {
115
+ names = readdirSync(root).filter((name) => name.startsWith('.initialized-') && name.endsWith('.json'));
116
+ } catch {
117
+ return [];
118
+ }
119
+ return names
120
+ .filter((name) => {
121
+ if (hashed.has(name)) return true;
122
+ try {
123
+ const marker = JSON.parse(readFileSync(join(root, name), 'utf8'));
124
+ return typeof marker?.dataDir === 'string' && normalizeDataDir(marker.dataDir) === dataDirKey;
125
+ } catch {
126
+ return false;
127
+ }
128
+ })
129
+ .map((name) => join(root, name));
130
+ }
131
+
132
+ /**
133
+ * Evidence that a data dir without the in-dir marker was initialized by an
134
+ * older build: a legacy marker for it, or an unscoped backup whose manifest
135
+ * names it. Null when there is none, or when the data dir was already migrated
136
+ * (scoped backups exist), so a recreated profile stays fresh.
137
+ */
138
+ export function findLegacyUserDataEvidence(dataDir, { fresh = false } = {}) {
139
+ if (!dataDir || hasUserDataInitMarker(dataDir)) return null;
140
+ const key = normalizeDataDir(dataDir);
141
+ const roots = legacyBackupRoots().join('\n');
142
+ const hit = legacyEvidenceCache.get(key);
143
+ if (!fresh && hit && hit.roots === roots) return hit.evidence;
144
+ const evidence = scanLegacyUserDataEvidence(dataDir, key);
145
+ legacyEvidenceCache.set(key, { roots, evidence });
146
+ return evidence;
147
+ }
148
+
149
+ // Per-process legacy-evidence lookup for plain reads (a fresh install with a
150
+ // missing config would otherwise rescan the legacy root on every read). RMW
151
+ // writes always rescan; this process's own backups invalidate it.
152
+ const legacyEvidenceCache = new Map();
153
+
154
+ function scanLegacyUserDataEvidence(dataDir, key) {
155
+ if (backupDirsNewestFirst(scopeDirFor(dataDir)).length > 0) return null;
156
+ const markers = [];
157
+ const backups = [];
158
+ for (const root of legacyBackupRoots()) {
159
+ markers.push(...legacyMarkersFor(root, dataDir, key));
160
+ for (const name of backupDirsNewestFirst(root)) {
161
+ if (name.startsWith(SCOPE_PREFIX)) continue;
162
+ if (manifestDataDirMatches(join(root, name), key)) backups.push(join(root, name));
163
+ }
164
+ }
165
+ backups.sort((x, y) => basename(y).localeCompare(basename(x)));
166
+ return markers.length || backups.length ? { markers, backups, roots: legacyBackupRoots() } : null;
167
+ }
168
+
169
+ /** A missing config is a loss to recover (not a fresh install). */
170
+ export function isUserDataRecoveryExpected(dataDir, options) {
171
+ return hasUserDataInitMarker(dataDir) || findLegacyUserDataEvidence(dataDir, options) != null;
57
172
  }
58
173
 
59
174
  /** Skip single-section wipe remnants (e.g. `{ search: … }` only). */
@@ -64,10 +179,10 @@ function isStructurallyCompleteMixdogConfigBackup(parsed) {
64
179
  return true;
65
180
  }
66
181
 
67
- /** Backup directory names, newest first; empty when the root is unreadable. */
68
- function backupDirsNewestFirst() {
182
+ /** Backup directory names in `dir`, newest first; empty when unreadable. */
183
+ function backupDirsNewestFirst(dir) {
69
184
  try {
70
- return readdirSync(getBackupRoot(), { withFileTypes: true })
185
+ return readdirSync(dir, { withFileTypes: true })
71
186
  .filter((entry) => entry.isDirectory())
72
187
  .map((entry) => entry.name)
73
188
  .sort()
@@ -78,16 +193,71 @@ function backupDirsNewestFirst() {
78
193
  }
79
194
 
80
195
  /**
81
- * Newest backup first: return the first structurally complete mixdog-config.json
82
- * (skips degenerate single-section snapshots from a prior failed RMW).
196
+ * A backup is restorable only when its manifest names this data dir and this
197
+ * incarnation of it. Legacy unscoped backups carry no manifest, so they stay
198
+ * on disk but never match.
83
199
  */
84
- export function loadLatestMixdogConfigFromBackup(_dataDir) {
200
+ function readRestorableConfig(backupDir, dataDirKey, instanceId) {
201
+ try {
202
+ const manifest = JSON.parse(readFileSync(join(backupDir, BACKUP_MANIFEST), 'utf8'));
203
+ if (typeof manifest?.dataDir !== 'string' || normalizeDataDir(manifest.dataDir) !== dataDirKey) return null;
204
+ if (manifest.instanceId !== instanceId) return null;
205
+ const parsed = JSON.parse(readFileSync(join(backupDir, 'mixdog-config.json'), 'utf8'));
206
+ return isStructurallyCompleteMixdogConfigBackup(parsed) ? parsed : null;
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+
212
+ function findRestorableConfig(dataDir, instanceId) {
213
+ const key = normalizeDataDir(dataDir);
214
+ const scopeDir = scopeDirFor(dataDir);
215
+ for (const name of backupDirsNewestFirst(scopeDir)) {
216
+ const parsed = readRestorableConfig(join(scopeDir, name), key, instanceId);
217
+ if (parsed) return parsed;
218
+ }
85
219
  const root = getBackupRoot();
86
- for (const name of backupDirsNewestFirst()) {
87
- const cfgPath = join(root, name, 'mixdog-config.json');
88
- if (!existsSync(cfgPath)) continue;
220
+ for (const name of backupDirsNewestFirst(root)) {
221
+ if (name.startsWith(SCOPE_PREFIX)) continue;
222
+ const parsed = readRestorableConfig(join(root, name), key, instanceId);
223
+ if (parsed) return parsed;
224
+ }
225
+ return null;
226
+ }
227
+
228
+ // Per-process restore lookup, keyed by data dir. A hit is reused while the
229
+ // backup root and the marker's instanceId are unchanged; this process's own
230
+ // backups invalidate it.
231
+ const restoreLookupCache = new Map();
232
+
233
+ /**
234
+ * Newest structurally complete mixdog-config.json backed up from THIS data
235
+ * dir (skips degenerate single-section snapshots from a prior failed RMW).
236
+ * Never another data dir's config, and nothing for an uninitialized one.
237
+ */
238
+ export function loadLatestMixdogConfigFromBackup(dataDir, options) {
239
+ const instanceId = readInitInstanceId(dataDir);
240
+ if (!instanceId) return loadLegacyMixdogConfigFromBackup(dataDir, options);
241
+ const key = normalizeDataDir(dataDir);
242
+ const root = getBackupRoot();
243
+ let hit = restoreLookupCache.get(key);
244
+ if (!hit || hit.root !== root || hit.instanceId !== instanceId) {
245
+ hit = { root, instanceId, config: findRestorableConfig(dataDir, instanceId) };
246
+ restoreLookupCache.set(key, hit);
247
+ }
248
+ return hit.config ? structuredClone(hit.config) : null;
249
+ }
250
+
251
+ /**
252
+ * Upgraded data dir without the in-dir marker: only an unscoped backup whose
253
+ * manifest names this data dir is verifiable (legacy backups predate
254
+ * instanceIds); manifest-less legacy backups are never restored.
255
+ */
256
+ function loadLegacyMixdogConfigFromBackup(dataDir, options) {
257
+ const evidence = findLegacyUserDataEvidence(dataDir, options);
258
+ for (const backupDir of evidence?.backups || []) {
89
259
  try {
90
- const parsed = JSON.parse(readFileSync(cfgPath, 'utf8'));
260
+ const parsed = JSON.parse(readFileSync(join(backupDir, 'mixdog-config.json'), 'utf8'));
91
261
  if (isStructurallyCompleteMixdogConfigBackup(parsed)) return parsed;
92
262
  } catch {}
93
263
  }
@@ -108,38 +278,83 @@ function copyTree(src, dst, copied) {
108
278
  copied.push(dst);
109
279
  }
110
280
 
111
- function pruneBackups(keep = 40) {
112
- for (const name of backupDirsNewestFirst().slice(keep)) {
281
+ /** Prune one data dir's scope only; other scopes and legacy entries stay. */
282
+ function pruneBackups(scopeDir, keep = 40) {
283
+ for (const name of backupDirsNewestFirst(scopeDir).slice(keep)) {
113
284
  try {
114
- rmSync(join(getBackupRoot(), name), { recursive: true, force: true });
285
+ rmSync(join(scopeDir, name), { recursive: true, force: true });
115
286
  } catch {}
116
287
  }
117
288
  }
118
289
 
290
+ /**
291
+ * Create the data dir's init marker unless one exists; returns its instanceId
292
+ * (null when the data dir is gone or unwritable). Never creates the data dir.
293
+ */
119
294
  export function markUserDataInitialized(dataDir) {
295
+ const existing = readInitInstanceId(dataDir);
296
+ if (existing || !dataDir || !existsSync(dataDir)) return existing;
120
297
  try {
121
- mkdirSync(getBackupRoot(), { recursive: true });
122
298
  writeFileSync(
123
- initMarkerPath(dataDir),
299
+ join(dataDir, INIT_MARKER),
124
300
  `${JSON.stringify(
125
- {
126
- dataDir,
127
- updatedAt: new Date().toISOString(),
128
- },
301
+ { instanceId: randomUUID(), dataDir: resolve(dataDir), createdAt: new Date().toISOString() },
129
302
  null,
130
303
  2
131
304
  )}\n`,
132
- 'utf8'
305
+ // An unreadable marker is replaced; a concurrent creator's valid one wins.
306
+ { encoding: 'utf8', flag: existsSync(join(dataDir, INIT_MARKER)) ? 'w' : 'wx' }
133
307
  );
134
308
  } catch {}
309
+ return readInitInstanceId(dataDir);
310
+ }
311
+
312
+ const ensuredDataDirs = new Set();
313
+
314
+ /**
315
+ * Called when a readable config is observed. A data dir that predates the
316
+ * in-dir marker (an upgraded install) gets its marker together with a first
317
+ * scoped snapshot, so a later loss of the config stays recoverable. Once per
318
+ * data dir per process.
319
+ */
320
+ export function ensureUserDataInitialized(dataDir) {
321
+ if (!dataDir) return;
322
+ const key = normalizeDataDir(dataDir);
323
+ if (ensuredDataDirs.has(key)) return;
324
+ ensuredDataDirs.add(key);
325
+ if (!hasUserDataInitMarker(dataDir)) backupUserData(dataDir, 'initial-snapshot');
326
+ }
327
+
328
+ function skipBackups() {
329
+ return process.env.MIXDOG_SKIP_USER_DATA_BACKUP === '1' || process.env.MIXDOG_SKIP_USER_DATA_BACKUP === 'true';
330
+ }
331
+
332
+ function backupTarget(dataDir, reason) {
333
+ const scopeDir = scopeDirFor(dataDir);
334
+ return { scopeDir, backupDir: join(scopeDir, `${stamp()}-${safeReason(reason)}`) };
335
+ }
336
+
337
+ // Mark first so the manifest carries this incarnation's instanceId; the
338
+ // manifest goes last, so an interrupted copy is never restorable.
339
+ function finishBackup(dataDir, backupDir, reason) {
340
+ const instanceId = markUserDataInitialized(dataDir);
341
+ writeFileSync(
342
+ join(backupDir, BACKUP_MANIFEST),
343
+ `${JSON.stringify(
344
+ { dataDir: resolve(dataDir), instanceId, reason, createdAt: new Date().toISOString() },
345
+ null,
346
+ 2
347
+ )}\n`,
348
+ 'utf8'
349
+ );
350
+ restoreLookupCache.delete(normalizeDataDir(dataDir));
351
+ legacyEvidenceCache.delete(normalizeDataDir(dataDir));
135
352
  }
136
353
 
137
354
  export function backupUserData(dataDir, reason = 'snapshot') {
138
- if (process.env.MIXDOG_SKIP_USER_DATA_BACKUP === '1' || process.env.MIXDOG_SKIP_USER_DATA_BACKUP === 'true') {
139
- return { dir: null, copied: [] };
140
- }
355
+ if (skipBackups()) return { dir: null, copied: [] };
141
356
  if (!dataDir || !existsSync(dataDir)) return { dir: null, copied: [] };
142
- const backupDir = join(getBackupRoot(), `${stamp()}-${safeReason(reason)}`);
357
+ const { scopeDir, backupDir } = backupTarget(dataDir, reason);
143
358
  const copied = [];
144
359
  for (const rel of USER_DATA_FILES) {
145
360
  const src = join(dataDir, rel);
@@ -150,8 +365,8 @@ export function backupUserData(dataDir, reason = 'snapshot') {
150
365
  if (existsSync(src)) copyTree(src, join(backupDir, rel), copied);
151
366
  }
152
367
  if (copied.length > 0) {
153
- markUserDataInitialized(dataDir);
154
- pruneBackups();
368
+ finishBackup(dataDir, backupDir, reason);
369
+ pruneBackups(scopeDir);
155
370
  if (process.env.MIXDOG_SETUP_QUIET !== '1') {
156
371
  process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}\n`);
157
372
  }
@@ -177,10 +392,10 @@ async function copyTreeAsync(src, dst, copied) {
177
392
  copied.push(dst);
178
393
  }
179
394
 
180
- async function pruneBackupsAsync(keep = 40) {
395
+ async function pruneBackupsAsync(scopeDir, keep = 40) {
181
396
  let entries = [];
182
397
  try {
183
- entries = (await readdirP(getBackupRoot(), { withFileTypes: true }))
398
+ entries = (await readdirP(scopeDir, { withFileTypes: true }))
184
399
  .filter((entry) => entry.isDirectory())
185
400
  .map((entry) => entry.name)
186
401
  .sort()
@@ -190,17 +405,15 @@ async function pruneBackupsAsync(keep = 40) {
190
405
  }
191
406
  for (const name of entries.slice(keep)) {
192
407
  try {
193
- await rmP(join(getBackupRoot(), name), { recursive: true, force: true });
408
+ await rmP(join(scopeDir, name), { recursive: true, force: true });
194
409
  } catch {}
195
410
  }
196
411
  }
197
412
 
198
413
  export async function backupUserDataAsync(dataDir, reason = 'snapshot') {
199
- if (process.env.MIXDOG_SKIP_USER_DATA_BACKUP === '1' || process.env.MIXDOG_SKIP_USER_DATA_BACKUP === 'true') {
200
- return { dir: null, copied: [] };
201
- }
414
+ if (skipBackups()) return { dir: null, copied: [] };
202
415
  if (!dataDir || !existsSync(dataDir)) return { dir: null, copied: [] };
203
- const backupDir = join(getBackupRoot(), `${stamp()}-${safeReason(reason)}`);
416
+ const { scopeDir, backupDir } = backupTarget(dataDir, reason);
204
417
  const copied = [];
205
418
  for (const rel of USER_DATA_FILES) {
206
419
  const src = join(dataDir, rel);
@@ -211,8 +424,8 @@ export async function backupUserDataAsync(dataDir, reason = 'snapshot') {
211
424
  if (existsSync(src)) await copyTreeAsync(src, join(backupDir, rel), copied);
212
425
  }
213
426
  if (copied.length > 0) {
214
- markUserDataInitialized(dataDir);
215
- await pruneBackupsAsync();
427
+ finishBackup(dataDir, backupDir, reason);
428
+ await pruneBackupsAsync(scopeDir);
216
429
  if (process.env.MIXDOG_SETUP_QUIET !== '1') {
217
430
  process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}\n`);
218
431
  }