mixdog 1.0.11 → 1.1.0

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 (471) hide show
  1. package/NOTICE.md +20 -0
  2. package/README.md +75 -181
  3. package/package.json +9 -4
  4. package/scripts/build-shipped-history.mjs +131 -0
  5. package/scripts/check-changelog-locales.mjs +203 -0
  6. package/scripts/computer-task-cost-report.mjs +0 -4
  7. package/scripts/lib/run-node-tests.mjs +235 -13
  8. package/scripts/lib/test-failure-records.mjs +5 -0
  9. package/scripts/lib/test-failure-summary.mjs +9 -3
  10. package/scripts/lib/test-spawn-ledger.mjs +19 -0
  11. package/scripts/prune-embedding-runtime.mjs +9 -5
  12. package/scripts/sync-client-version-floors.mjs +100 -0
  13. package/scripts/test-direct.mjs +16 -1
  14. package/src/agents/advisor/AGENT.md +38 -0
  15. package/src/agents/advisor/agent.json +6 -0
  16. package/src/agents/reviewer/AGENT.md +5 -2
  17. package/src/agents/reviewer/agent.json +1 -1
  18. package/src/defaults/shipped-history.json +381 -0
  19. package/src/defaults/skills/docx/SKILL.md +7 -5
  20. package/src/defaults/skills/docx/references/html.md +9 -8
  21. package/src/defaults/skills/docx/references/native-authoring.md +28 -18
  22. package/src/defaults/skills/goal-management/SKILL.md +11 -5
  23. package/src/defaults/skills/local-provider/SKILL.md +2 -1
  24. package/src/defaults/skills/pdf/SKILL.md +9 -6
  25. package/src/defaults/skills/pdf/references/html.md +17 -12
  26. package/src/defaults/skills/pptx/SKILL.md +8 -6
  27. package/src/defaults/skills/pptx/references/composition.md +4 -4
  28. package/src/defaults/skills/pptx/references/concepts.md +168 -0
  29. package/src/defaults/skills/pptx/references/direction.md +5 -4
  30. package/src/defaults/skills/pptx/references/html.md +6 -29
  31. package/src/defaults/skills/pptx/references/kit-index.md +11 -3
  32. package/src/defaults/skills/pptx/references/kit.md +27 -5
  33. package/src/defaults/skills/pptx/references/review.md +6 -5
  34. package/src/defaults/skills/setup/SKILL.md +5 -8
  35. package/src/defaults/skills/setup/references/actions.md +61 -31
  36. package/src/defaults/skills/setup/references/surfaces.md +12 -10
  37. package/src/defaults/skills/xlsx/SKILL.md +8 -6
  38. package/src/defaults/skills/xlsx/references/html.md +8 -4
  39. package/src/defaults/skills/xlsx/references/model-conventions.md +2 -0
  40. package/src/defaults/skills/xlsx/references/report-design.md +32 -16
  41. package/src/help.mjs +1 -1
  42. package/src/lib/rules-builder.cjs +1 -1
  43. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +3 -1
  44. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
  45. package/src/runtime/agent/orchestrator/config-presets.d.mts +1 -0
  46. package/src/runtime/agent/orchestrator/config-presets.mjs +44 -0
  47. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +9 -1
  48. package/src/runtime/agent/orchestrator/config.mjs +102 -21
  49. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +3 -2
  50. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +90 -26
  51. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +44 -14
  52. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +4 -4
  53. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +5 -3
  54. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +79 -8
  55. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +29 -4
  56. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +10 -1
  58. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +11 -2
  59. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +9 -5
  60. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +9 -1
  61. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +2 -0
  62. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +30 -22
  63. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +2 -0
  64. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +28 -10
  65. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +14 -72
  66. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +19 -3
  67. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +20 -10
  68. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-sink.mjs +36 -9
  69. package/src/runtime/agent/orchestrator/providers/cursor.mjs +11 -17
  70. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +388 -0
  71. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +92 -109
  72. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +107 -0
  73. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +138 -30
  74. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +32 -15
  75. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +2 -0
  76. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +39 -5
  77. package/src/runtime/agent/orchestrator/providers/gemini.mjs +9 -2
  78. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +3 -2
  79. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +6 -1
  80. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +10 -1
  81. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +3 -2
  82. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +5 -1
  83. package/src/runtime/agent/orchestrator/providers/lib/note-error-usage.mjs +7 -0
  84. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +57 -0
  85. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +12 -5
  86. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +20 -15
  87. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +14 -9
  88. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +56 -11
  89. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +2 -1
  90. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +28 -1
  91. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +27 -3
  92. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +7 -5
  93. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +12 -0
  94. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -8
  95. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +26 -6
  96. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +7 -1
  97. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +34 -15
  98. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -2
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +12 -2
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +0 -6
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +0 -19
  102. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +12 -43
  103. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +6 -0
  104. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +39 -9
  105. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +31 -9
  106. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +13 -38
  107. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +5 -2
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +40 -9
  109. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +7 -39
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +28 -101
  111. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +8 -4
  112. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +3 -6
  113. package/src/runtime/agent/orchestrator/providers/openai-service-tier.mjs +19 -0
  114. package/src/runtime/agent/orchestrator/providers/openai-startup-prewarm.mjs +5 -195
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +9 -18
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +4 -66
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +11 -5
  118. package/src/runtime/agent/orchestrator/providers/openai-ws-send/acquire-attempt.mjs +3 -15
  119. package/src/runtime/agent/orchestrator/providers/openai-ws-send/attempt-request.mjs +6 -56
  120. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +1 -19
  121. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +2 -1
  123. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +3 -0
  124. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure-stamps.mjs +0 -1
  125. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +3 -1
  126. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts.mjs +3 -5
  127. package/src/runtime/agent/orchestrator/providers/openai-ws-send-outcome.mjs +10 -89
  128. package/src/runtime/agent/orchestrator/providers/openai-ws-send-span.mjs +1 -1
  129. package/src/runtime/agent/orchestrator/providers/openai-ws-send-trace.mjs +0 -6
  130. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +34 -11
  131. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +0 -1
  132. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +3 -7
  133. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +44 -19
  134. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +40 -12
  135. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +70 -0
  136. package/src/runtime/agent/orchestrator/providers/registry.mjs +38 -3
  137. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +4 -0
  138. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +1 -0
  139. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +2 -0
  140. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +11 -0
  141. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +31 -41
  142. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +5 -0
  143. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +0 -17
  144. package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +0 -3
  145. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +0 -1
  146. package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +1 -1
  148. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -2
  149. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +18 -4
  150. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +8 -1
  151. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -1
  152. package/src/runtime/agent/orchestrator/runtime-core/session-route-policy.mjs +16 -0
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +2 -0
  154. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -2
  155. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +7 -7
  156. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -0
  157. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +45 -2
  158. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +5 -3
  159. package/src/runtime/agent/orchestrator/session/eager-dispatch/entry.mjs +5 -0
  160. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +6 -13
  161. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +6 -2
  162. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +114 -0
  163. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +2 -0
  164. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +5 -9
  165. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +51 -18
  166. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +7 -4
  167. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
  168. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +0 -2
  169. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +6 -9
  170. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +63 -3
  171. package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +10 -28
  172. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +4 -4
  173. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +13 -0
  174. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +10 -19
  175. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +7 -1
  176. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +1 -0
  177. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +1 -3
  178. package/src/runtime/agent/orchestrator/stall-policy.mjs +0 -1
  179. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +18 -8
  180. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +19 -20
  181. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +6 -11
  182. package/src/runtime/agent/orchestrator/tools/builtin/file-version-test-helpers.mjs +11 -0
  183. package/src/runtime/agent/orchestrator/tools/builtin/file-version.mjs +13 -0
  184. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +8 -5
  185. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +25 -12
  186. package/src/runtime/agent/orchestrator/tools/builtin/raw-content-publication.mjs +17 -0
  187. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +136 -76
  188. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +15 -17
  189. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -2
  190. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +11 -10
  191. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -3
  192. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +30 -16
  193. package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +2 -2
  194. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +4 -11
  195. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +7 -3
  196. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-validation.mjs +16 -19
  197. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  198. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background.mjs +4 -1
  199. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +14 -4
  200. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +58 -8
  201. package/src/runtime/agent/orchestrator/tools/patch/content-guard.mjs +47 -0
  202. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +155 -0
  203. package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-proofs.mjs +6 -2
  204. package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-snapshots.mjs +6 -2
  205. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +19 -16
  206. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +13 -44
  207. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
  208. package/src/runtime/agent/orchestrator/tools/patch/post-patch-excerpts.mjs +5 -2
  209. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +47 -7
  210. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +72 -8
  211. package/src/runtime/agent/orchestrator/tools/patch/section-coalesce.mjs +3 -2
  212. package/src/runtime/agent/orchestrator/tools/patch/sequence/run.mjs +9 -1
  213. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +9 -1
  214. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +6 -6
  215. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +16 -4
  216. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +35 -14
  217. package/src/runtime/agent/orchestrator/tools/patch/wave.mjs +42 -4
  218. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  219. package/src/runtime/agent/orchestrator/tools/shell-codepage.mjs +296 -0
  220. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +75 -24
  221. package/src/runtime/browser-bridge/tool-defs.mjs +1 -0
  222. package/src/runtime/channels/lib/config.mjs +3 -8
  223. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +7 -3
  224. package/src/{tui → runtime/channels}/lib/voice-setup.mjs +38 -22
  225. package/src/runtime/channels/lib/webhook/request-handler.mjs +42 -8
  226. package/src/runtime/computer-bridge/actions.mjs +2 -0
  227. package/src/runtime/effort-judge/judge-client.mjs +125 -25
  228. package/src/runtime/effort-judge/judge-input.mjs +96 -0
  229. package/src/runtime/effort-judge/judge-worker.mjs +52 -20
  230. package/src/runtime/effort-judge/model-install.mjs +74 -0
  231. package/src/runtime/effort-judge/model-manifest.json +21 -0
  232. package/src/runtime/effort-judge/previous-human-request.mjs +162 -0
  233. package/src/runtime/effort-judge/verified-context-input.mjs +10 -0
  234. package/src/runtime/github/tool.mjs +214 -14
  235. package/src/runtime/local-provider/context-settings.mjs +15 -6
  236. package/src/runtime/local-provider/gguf-header.mjs +13 -5
  237. package/src/runtime/local-provider/hugging-face.mjs +27 -5
  238. package/src/runtime/local-provider/install-progress.mjs +7 -0
  239. package/src/runtime/local-provider/managed-runtime.mjs +2 -1
  240. package/src/runtime/local-provider/model-maintenance.mjs +15 -6
  241. package/src/runtime/local-provider/resumable-installations.mjs +40 -1
  242. package/src/runtime/local-provider/server.mjs +2 -0
  243. package/src/runtime/memory/lib/embedding-provider.mjs +12 -0
  244. package/src/runtime/memory/lib/http-router/session-routes.mjs +2 -1
  245. package/src/runtime/memory/lib/memory-config-flags.mjs +3 -2
  246. package/src/runtime/memory/lib/memory-embed.mjs +5 -0
  247. package/src/runtime/memory/lib/pg/process.mjs +57 -5
  248. package/src/runtime/office/README.md +13 -7
  249. package/src/runtime/office/authoring/docx-html-build.mjs +4 -4
  250. package/src/runtime/office/authoring/docx-html-measure.mjs +60 -0
  251. package/src/runtime/office/authoring/pdf-author-action.mjs +6 -1
  252. package/src/runtime/office/authoring/pptx-brief.mjs +40 -1
  253. package/src/runtime/office/authoring/pptx-html-build.mjs +1 -1
  254. package/src/runtime/office/authoring/pptx-html-measure.mjs +1 -1
  255. package/src/runtime/office/bench/deck-bench.mjs +1 -1
  256. package/src/runtime/office/com/office-com-host.ps1 +8 -0
  257. package/src/runtime/office/core/office-actions-render.mjs +2 -0
  258. package/src/runtime/office/design/design-art-direction.mjs +10 -2
  259. package/src/runtime/office/design/design-discipline.mjs +4 -5
  260. package/src/runtime/office/design/design-tokens.mjs +20 -1
  261. package/src/runtime/office/design/docx/design-docx/front-matter.mjs +7 -4
  262. package/src/runtime/office/design/docx/design-docx/section.mjs +3 -1
  263. package/src/runtime/office/design/docx/design-docx/writer.mjs +1 -0
  264. package/src/runtime/office/design/docx/design-docx-components.mjs +19 -15
  265. package/src/runtime/office/design/docx/design-docx.mjs +5 -6
  266. package/src/runtime/office/design/docx/document-typography.mjs +20 -6
  267. package/src/runtime/office/design/xlsx/design-xlsx.mjs +9 -2
  268. package/src/runtime/office/pdf/document-preview.mjs +133 -12
  269. package/src/runtime/office/pdf/pdf-writer.mjs +56 -22
  270. package/src/runtime/office/portable/portable-snapshot-docx.mjs +46 -3
  271. package/src/runtime/office/portable/portable-snapshot-shared.mjs +27 -1
  272. package/src/runtime/office/quality/assurance-rendered.mjs +21 -5
  273. package/src/runtime/office/quality/assurance-structure-docx.mjs +41 -9
  274. package/src/runtime/office/quality/design-aesthetics.mjs +4 -2
  275. package/src/runtime/office/quality/design-deck-diversity.mjs +15 -3
  276. package/src/runtime/office/quality/design-review.mjs +13 -5
  277. package/src/runtime/office/quality/document-brief.mjs +8 -2
  278. package/src/runtime/office/quality/forced-break-pages.mjs +203 -0
  279. package/src/runtime/office/quality/pptx-deck-rubric.mjs +21 -3
  280. package/src/runtime/office/quality/quality-pipeline.mjs +14 -17
  281. package/src/runtime/session-search/session-search-db.mjs +183 -0
  282. package/src/runtime/session-search/session-search-ingest.mjs +106 -0
  283. package/src/runtime/shared/agent-route-config.mjs +50 -2
  284. package/src/runtime/shared/bounded-file-read.mjs +166 -0
  285. package/src/runtime/shared/builtin-grandfathering.mjs +37 -0
  286. package/src/runtime/shared/config.mjs +24 -4
  287. package/src/runtime/shared/defaults-separation.mjs +263 -0
  288. package/src/runtime/shared/error-presentation.mjs +8 -1
  289. package/src/runtime/shared/llm/anthropic-effort.mjs +4 -0
  290. package/src/runtime/shared/llm/cost.mjs +4 -2
  291. package/src/runtime/shared/llm/model-catalog-projection.mjs +1 -1
  292. package/src/runtime/shared/llm/model-catalog.mjs +25 -0
  293. package/src/runtime/shared/llm/reasoning-usage.mjs +1 -2
  294. package/src/runtime/shared/llm/usage-accounting.mjs +14 -4
  295. package/src/runtime/shared/llm/usage-context.mjs +11 -1
  296. package/src/runtime/shared/llm/usage-ledger-repair.mjs +3 -1
  297. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +105 -1
  298. package/src/runtime/shared/llm/usage-ledger-worker.mjs +2 -1
  299. package/src/runtime/shared/llm/usage-ledger.mjs +72 -5
  300. package/src/runtime/shared/onnx-runtime-support.mjs +6 -0
  301. package/src/runtime/shared/orchestration.mjs +14 -6
  302. package/src/runtime/shared/pristine-execution-contract.json +7 -2
  303. package/src/runtime/shared/projects.mjs +65 -48
  304. package/src/runtime/shared/provider-accounts.mjs +47 -6
  305. package/src/runtime/shared/quota-resume-store.mjs +255 -0
  306. package/src/runtime/shared/shipped-definitions.mjs +275 -0
  307. package/src/runtime/shared/tool-card-model/agent-surface.mjs +30 -9
  308. package/src/runtime/shared/tool-card-model/aggregate-card.mjs +7 -3
  309. package/src/runtime/shared/tool-card-model/background-task.mjs +17 -1
  310. package/src/runtime/shared/tool-card-model/card-detail.mjs +10 -4
  311. package/src/runtime/shared/tool-card-model/card-header.mjs +10 -4
  312. package/src/runtime/shared/tool-card-model/card-status.mjs +22 -5
  313. package/src/runtime/shared/tool-card-model/displayed-result.mjs +6 -1
  314. package/src/runtime/shared/tool-card-model/media-result.mjs +53 -0
  315. package/src/runtime/shared/tool-card-model/terminal-status.mjs +17 -1
  316. package/src/runtime/shared/tool-card-model.golden-cases.mjs +1 -1
  317. package/src/runtime/shared/tool-card-model.golden.json +75 -66
  318. package/src/runtime/shared/tool-card-model.mjs +1 -0
  319. package/src/runtime/shared/tool-output-limit.mjs +3 -0
  320. package/src/runtime/shared/tool-primitives.mjs +24 -18
  321. package/src/runtime/shared/tool-result-summary.mjs +128 -17
  322. package/src/runtime/shared/tool-status.mjs +20 -7
  323. package/src/runtime/shared/tool-surface.mjs +105 -28
  324. package/src/runtime/shared/tool-work-units.mjs +221 -18
  325. package/src/runtime/shared/transcript-metadata.mjs +2 -0
  326. package/src/runtime/shared/turn-review-reply.mjs +2 -0
  327. package/src/runtime/shared/turn-snapshot.mjs +65 -6
  328. package/src/runtime/shared/turn-worktree-snapshot.mjs +202 -22
  329. package/src/runtime/shared/worker-requests.mjs +14 -2
  330. package/src/runtime/terminal-bridge/client.mjs +311 -0
  331. package/src/runtime/terminal-bridge/tool-defs.mjs +36 -0
  332. package/src/runtime/web-search/lib/ssrf-guard.mjs +12 -56
  333. package/src/runtime/web-search/tool-defs.mjs +2 -0
  334. package/src/session-runtime/boot/apis.mjs +30 -5
  335. package/src/session-runtime/boot/config.mjs +4 -0
  336. package/src/session-runtime/boot/facade.mjs +4 -1
  337. package/src/session-runtime/boot/infrastructure.mjs +21 -2
  338. package/src/session-runtime/boot/session.mjs +5 -5
  339. package/src/session-runtime/boot/tools.mjs +2 -0
  340. package/src/session-runtime/builtin-features.d.mts +1 -0
  341. package/src/session-runtime/channel-config-api.mjs +5 -0
  342. package/src/session-runtime/config-lifecycle/config-writers.mjs +3 -0
  343. package/src/session-runtime/config-lifecycle.mjs +31 -6
  344. package/src/session-runtime/goal-facade-api.mjs +4 -0
  345. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +15 -3
  346. package/src/session-runtime/lifecycle/inheritance.mjs +28 -6
  347. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +3 -0
  348. package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -1
  349. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +9 -0
  350. package/src/session-runtime/lifecycle/teardown/session-close.mjs +14 -2
  351. package/src/session-runtime/lifecycle/teardown.mjs +5 -0
  352. package/src/session-runtime/local-provider-settings.mjs +10 -0
  353. package/src/session-runtime/media-api.mjs +32 -22
  354. package/src/session-runtime/model-recency.mjs +14 -0
  355. package/src/session-runtime/model-route/route-persist.mjs +17 -2
  356. package/src/session-runtime/model-route/route-tuning.mjs +16 -5
  357. package/src/session-runtime/model-route/set-route.mjs +55 -22
  358. package/src/session-runtime/model-route/web-search-route.mjs +2 -2
  359. package/src/session-runtime/model-route-api.mjs +2 -1
  360. package/src/session-runtime/notification-bus.mjs +31 -6
  361. package/src/session-runtime/provider-auth/login.mjs +6 -0
  362. package/src/session-runtime/provider-models/catalog-load.mjs +3 -1
  363. package/src/session-runtime/provider-models/row-hydration.mjs +36 -10
  364. package/src/session-runtime/quick-model-rows/model-meta.mjs +26 -0
  365. package/src/session-runtime/quick-model-rows/provider-rows.mjs +40 -14
  366. package/src/session-runtime/resource-api.mjs +8 -1
  367. package/src/session-runtime/route-state.mjs +9 -0
  368. package/src/session-runtime/runtime-core.mjs +4 -0
  369. package/src/session-runtime/runtime-facade.mjs +20 -1
  370. package/src/session-runtime/runtime-feature-gates.mjs +2 -0
  371. package/src/session-runtime/runtime-review-api.mjs +12 -1
  372. package/src/session-runtime/services/agent-tool/helpers.mjs +14 -1
  373. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +10 -15
  374. package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +6 -1
  375. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +20 -2
  376. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +14 -6
  377. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +2 -0
  378. package/src/session-runtime/services/agent-tool/tag-registry.mjs +12 -1
  379. package/src/session-runtime/services/agent-tool/worker-index.mjs +17 -1
  380. package/src/session-runtime/services/agent-tool.mjs +1 -0
  381. package/src/session-runtime/services/channel-admin.mjs +87 -13
  382. package/src/session-runtime/services/provider-admin.mjs +306 -66
  383. package/src/session-runtime/services/seeds.mjs +73 -142
  384. package/src/session-runtime/services/usage-dashboard-model.mjs +29 -0
  385. package/src/session-runtime/services/usage-dashboard.mjs +7 -0
  386. package/src/session-runtime/session-address.mjs +125 -0
  387. package/src/session-runtime/session-create.mjs +57 -2
  388. package/src/session-runtime/settings-api.mjs +1 -0
  389. package/src/session-runtime/settings-builtin-tools-api.mjs +15 -10
  390. package/src/session-runtime/settings-compaction-api.mjs +13 -0
  391. package/src/session-runtime/setup-tool/executor.mjs +93 -40
  392. package/src/session-runtime/setup-tool/extended-actions.mjs +64 -5
  393. package/src/session-runtime/setup-tool/settings-contract.mjs +46 -3
  394. package/src/session-runtime/setup-tool/tool-defs.mjs +14 -6
  395. package/src/session-runtime/tool-surface.mjs +16 -9
  396. package/src/session-runtime/turn/session-ops.mjs +3 -0
  397. package/src/session-runtime/turn/share-ops.mjs +4 -1
  398. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +6 -0
  399. package/src/session-runtime/usage-stats-api.mjs +43 -3
  400. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +31 -5
  401. package/src/session-runtime/workflow-agents-api/agent-route.mjs +6 -2
  402. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +27 -7
  403. package/src/standalone/daemon-boot-coordinator.mjs +10 -0
  404. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  405. package/src/standalone/daemon-drain-policy.mjs +9 -0
  406. package/src/standalone/daemon-stored-session-views.mjs +7 -8
  407. package/src/standalone/daemon.mjs +36 -8
  408. package/src/standalone/local-session-runtime.mjs +11 -0
  409. package/src/standalone/session-client.mjs +52 -7
  410. package/src/standalone/session-protocol.mjs +9 -0
  411. package/src/standalone/session-service/agent-tree/agent-child.mjs +2 -0
  412. package/src/standalone/session-service/entries/lifecycle.mjs +2 -0
  413. package/src/standalone/session-service/entries/session-loads.mjs +9 -0
  414. package/src/standalone/session-service/projection/revisions.mjs +28 -4
  415. package/src/standalone/session-service/projection.mjs +1 -0
  416. package/src/standalone/session-service/quota-resume.mjs +140 -0
  417. package/src/standalone/session-service/session-calls/session-action.mjs +2 -2
  418. package/src/standalone/session-service/session-calls.mjs +10 -2
  419. package/src/standalone/session-service/stored-reader.mjs +26 -1
  420. package/src/standalone/session-service.mjs +21 -0
  421. package/src/standalone/session-transport/routes.mjs +2 -0
  422. package/src/tui/app/doctor.mjs +7 -11
  423. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +3 -5
  424. package/src/tui/app/model-picker/model-footer.mjs +2 -2
  425. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +21 -2
  426. package/src/tui/app/model-picker/route-selection.mjs +6 -1
  427. package/src/tui/app/settings-picker/settings-rows.mjs +4 -6
  428. package/src/tui/app/settings-picker/settings-toggles/store-toggles.mjs +1 -1
  429. package/src/tui/app/slash-commands.mjs +1 -1
  430. package/src/tui/app/slash-dispatch/route-commands.mjs +5 -3
  431. package/src/tui/app/transcript-row-estimate.mjs +17 -5
  432. package/src/tui/components/ContextPanel.jsx +5 -1
  433. package/src/tui/components/Spinner.jsx +34 -319
  434. package/src/tui/components/SpinnerByline.jsx +256 -0
  435. package/src/tui/components/tool-execution/aggregate-card.jsx +4 -2
  436. package/src/tui/components/tool-execution/text-format.mjs +1 -0
  437. package/src/tui/components/use-spinner-progress.mjs +113 -0
  438. package/src/tui/dist/index.mjs +15896 -14452
  439. package/src/tui/session/agent-job-feed/execution-delivery.mjs +13 -1
  440. package/src/tui/session/agent-job-feed/notification-router.mjs +3 -0
  441. package/src/tui/session/agent-job-feed/pending-resume.mjs +14 -1
  442. package/src/tui/session/agent-job-feed.mjs +4 -0
  443. package/src/tui/session/context-state.mjs +13 -1
  444. package/src/tui/session/initial-state.mjs +6 -0
  445. package/src/tui/session/quota-resume.mjs +263 -0
  446. package/src/tui/session/session-api/intake/abort.mjs +39 -35
  447. package/src/tui/session/session-api/intake/submission.mjs +13 -21
  448. package/src/tui/session/session-api/intake.mjs +3 -3
  449. package/src/tui/session/session-api/integrations.mjs +5 -1
  450. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  451. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -2
  452. package/src/tui/session/session-api/lifecycle/session-reset.mjs +4 -0
  453. package/src/tui/session/session-api/lifecycle.mjs +71 -2
  454. package/src/tui/session/session-api/media.mjs +2 -2
  455. package/src/tui/session/session-api/quota-resume.mjs +21 -0
  456. package/src/tui/session/session-api/routes.mjs +5 -2
  457. package/src/tui/session/session-api/settings.mjs +2 -0
  458. package/src/tui/session/session-api.mjs +2 -0
  459. package/src/tui/session/session-flow/auto-clear.mjs +14 -6
  460. package/src/tui/session/session-flow/drain.mjs +52 -5
  461. package/src/tui/session/session-flow/queue/take-entries.mjs +9 -4
  462. package/src/tui/session/session-flow/queue.mjs +4 -0
  463. package/src/tui/session/session-flow/steering.mjs +11 -0
  464. package/src/tui/session/session-flow.mjs +1 -0
  465. package/src/tui/session/tool-approval.mjs +7 -2
  466. package/src/tui/session/transcript-intake.mjs +1 -0
  467. package/src/tui/session/turn-tool-cards.mjs +4 -6
  468. package/src/tui/session/turn.mjs +40 -4
  469. package/src/vendor/statusline/bin/statusline-route.mjs +3 -2
  470. package/src/vendor/statusline/src/gateway/route-meta.mjs +1 -1
  471. package/src/runtime/agent/orchestrator/providers/openai-ws-warmup.mjs +0 -165
@@ -583,6 +583,8 @@ function lookupModelMetadata(originalId, provider, catalog, modelsDevCatalog) {
583
583
  preserveBaseLimits: !nativeLimitsAuthoritative,
584
584
  });
585
585
  }
586
+ if (!meta && String(provider || '').startsWith('custom-'))
587
+ return customCatalogDefaults(originalId, catalog, modelsDevCatalog);
586
588
  return meta
587
589
  ? {
588
590
  ...meta,
@@ -592,16 +594,39 @@ function lookupModelMetadata(originalId, provider, catalog, modelsDevCatalog) {
592
594
  : null;
593
595
  }
594
596
 
597
+ // A user-defined (`custom-*`) provider is in no catalog under its own id, so
598
+ // its discovered models match by bare model id (as listed, then without a
599
+ // `vendor/` route prefix) under the vendor the id names. Only the reasoning
600
+ // capabilities become defaults for the custom row (its overrides win); prices
601
+ // and limits describe the vendor's own API, not the custom endpoint.
602
+ function customCatalogDefaults(originalId, catalog, modelsDevCatalog) {
603
+ const listed = String(originalId || '');
604
+ for (const id of new Set([listed, listed.slice(listed.lastIndexOf('/') + 1)])) {
605
+ if (!id) continue;
606
+ const vendor = _relayedModelVendor(id) || _modelsDevProviderId(catalog[id]?.litellm_provider);
607
+ const meta = lookupModelMetadata(id, vendor, catalog, modelsDevCatalog);
608
+ if (!meta) continue;
609
+ return {
610
+ supportsReasoning: meta.supportsReasoning === true,
611
+ reasoningOptions: Array.isArray(meta.reasoningOptions) ? meta.reasoningOptions : [],
612
+ reasoningContentField: meta.reasoningContentField || null,
613
+ };
614
+ }
615
+ return null;
616
+ }
617
+
595
618
  function _normalize(entry) {
596
619
  if (!entry || typeof entry !== 'object') return null;
597
620
  // OpenAI's Priority processing (since renamed Fast mode) columns.
598
621
  const fastPricing = litellmPricing(entry, '_priority');
622
+ const ultrafastPricing = litellmPricing(entry, '_ultrafast');
599
623
  return {
600
624
  contextWindow: entry.max_input_tokens || entry.max_tokens || null,
601
625
  outputTokens: entry.max_output_tokens || null,
602
626
  ...litellmPricing(entry),
603
627
  ...litellmMediaPricing(entry),
604
628
  ...(PRICING_RATE_KEYS.some((key) => fastPricing[key] != null) ? { fastPricing } : {}),
629
+ ...(PRICING_RATE_KEYS.some((key) => ultrafastPricing[key] != null) ? { ultrafastPricing } : {}),
605
630
  ...(entry.off_peak_multiplier ? { offPeakMultiplier: entry.off_peak_multiplier } : {}),
606
631
  supportsVision: entry.supports_vision === true,
607
632
  supportsFunctionCalling: entry.supports_function_calling === true,
@@ -25,8 +25,7 @@ export function reasoningUsage(usage) {
25
25
  raw?.thoughts_token_count,
26
26
  ];
27
27
  const reasoningTokens = values.map(tokenCount).find((value) => value !== null) ?? null;
28
- const measured = { reasoningTokens, reasoningTokensComplete: reasoningTokens !== null };
29
- return raw?.warmup_usage ? combineReasoningUsage(measured, raw.warmup_usage) : measured;
28
+ return { reasoningTokens, reasoningTokensComplete: reasoningTokens !== null };
30
29
  }
31
30
 
32
31
  export function combineReasoningUsage(left, right) {
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { getUsageLedger, makeUsageRecord } from './usage-ledger.mjs';
3
- import { withUsageContext } from './usage-context.mjs';
3
+ import { isNotedUsage, withUsageContext } from './usage-context.mjs';
4
4
  import { ACCOUNT_PROVIDERS } from '../provider-accounts.mjs';
5
5
  import { currentProviderAccountId } from '../provider-auth-binding.mjs';
6
6
 
@@ -10,6 +10,14 @@ import { currentProviderAccountId } from '../provider-auth-binding.mjs';
10
10
  const recorded = new WeakSet();
11
11
  const hasTokens = (usage) =>
12
12
  ['inputTokens', 'outputTokens', 'cachedTokens', 'cacheWriteTokens'].some((key) => Number(usage?.[key]) > 0);
13
+ // One rule for every reported usage object: tokens, or a provider-reported
14
+ // cost (a cost-only report is still a billed request).
15
+ const recordable = (usage) =>
16
+ hasTokens(usage) ||
17
+ (usage?.costUsd != null &&
18
+ usage.costUsd !== '' &&
19
+ Number.isFinite(Number(usage.costUsd)) &&
20
+ Number(usage.costUsd) >= 0);
13
21
 
14
22
  /**
15
23
  * Runs at the common provider boundary, not inside optional diagnostic IO.
@@ -92,7 +100,7 @@ export async function accountProviderSend(provider, instance, send, model, opts
92
100
  };
93
101
  const saveAbandoned = async () => {
94
102
  for (const [index, attempt] of (identity.abandonedUsage || []).entries()) {
95
- if (hasTokens(attempt.usage)) await save(attempt, `${requestId}:abandoned:${index}`);
103
+ if (recordable(attempt.usage)) await save(attempt, `${requestId}:abandoned:${index}`);
96
104
  }
97
105
  };
98
106
  let result;
@@ -102,8 +110,10 @@ export async function accountProviderSend(provider, instance, send, model, opts
102
110
  await saveAbandoned();
103
111
  // Only provider-reported partial usage is recordable; never invent
104
112
  // tokens for a failed request or reinterpret an error as a success.
105
- if (error?.usage) await save(error);
106
- else if (hasTokens(error?.partialUsage))
113
+ // Usage already noted as an abandoned attempt was just recorded above.
114
+ if (error?.usage) {
115
+ if (!isNotedUsage(error.usage)) await save(error);
116
+ } else if (recordable(error?.partialUsage) && !isNotedUsage(error.partialUsage))
107
117
  await save({ usage: error.partialUsage, model: error.partialModel }, requestId, error);
108
118
  throw error;
109
119
  }
@@ -11,9 +11,19 @@ export const noteRequestServiceTier = (tier) => {
11
11
  const identity = context.getStore();
12
12
  if (identity) identity.requestServiceTier = tier || '';
13
13
  };
14
+ // Usage objects already handed to a send's accounting, as given and as
15
+ // stored. Noting is idempotent, and an error that surfaces later carrying the
16
+ // same usage (an earlier attempt's error re-thrown) is not recorded again.
17
+ const notedUsage = new WeakSet();
18
+ export const isNotedUsage = (usage) => typeof usage === 'object' && usage !== null && notedUsage.has(usage);
14
19
  // A provider-local retry abandons an attempt the provider still billed; the
15
20
  // enclosing send records it alongside its own final usage.
16
21
  export const noteAbandonedUsage = (usage, model) => {
17
22
  const identity = context.getStore();
18
- if (identity && usage) (identity.abandonedUsage ||= []).push({ usage, model });
23
+ if (!identity || !usage || notedUsage.has(usage)) return;
24
+ const stored = identity.normalizeAbandonedUsage?.(usage) ?? usage;
25
+ notedUsage.add(usage);
26
+ notedUsage.add(stored);
27
+ identity.abandonedUsage ||= [];
28
+ identity.abandonedUsage.push({ usage: stored, model });
19
29
  };
@@ -86,7 +86,9 @@ function* planUsageRepair(rows, { providerOverrides = {}, price = priceUsage })
86
86
  serviceTier: oldRates?.serviceTier,
87
87
  kind,
88
88
  ts: row.ts,
89
- historical: true,
89
+ // Same rule as at ingestion: a live record keeps its exact
90
+ // request time (time-of-day pricing applies); imports do not.
91
+ historical: signature[6] !== 'live',
90
92
  });
91
93
  cost = priced.costUsd;
92
94
  if (cost !== null && (!Number.isFinite(cost) || cost < 0)) throw new Error('Invalid repair price');
@@ -14,7 +14,7 @@
14
14
  * here mutates stored rows. Storage, schema and inserts stay in
15
15
  * usage-ledger.mjs.
16
16
  */
17
- import { isConversationUsageSource, num } from './usage-rollup.mjs';
17
+ import { isConversationUsageSource, num, usageRollupDayKey } from './usage-rollup.mjs';
18
18
  import { normalizeUsageMeasurement, normalizeLegacyUsageDay } from './usage-measurement.mjs';
19
19
 
20
20
  // A detail and its terminal summary can overlap on the SAME route.
@@ -329,6 +329,110 @@ export function readSessionUsage(db, sessionIds) {
329
329
  return { ...usage, speedOutput, speedMs };
330
330
  }
331
331
 
332
+ const DAY_KEY = (alias) => `printf('%04d-%02d-%02d',${alias}.day/10000,(${alias}.day/100)%100,${alias}.day%100)`;
333
+ const BUMP = (day) => `INSERT INTO day_revisions(day,rev) VALUES(${day},1) ON CONFLICT(day) DO UPDATE SET rev=rev+1;`;
334
+ const TRIGGER_ROWS = { INSERT: ['NEW'], DELETE: ['OLD'], UPDATE: ['OLD', 'NEW'] };
335
+ const triggers = (name, table, events, days) =>
336
+ events
337
+ .map(
338
+ (event) => `CREATE TRIGGER IF NOT EXISTS ${name}_${event.toLowerCase()} AFTER ${event} ON ${table}
339
+ BEGIN ${TRIGGER_ROWS[event].map((row) => BUMP(days(row))).join(' ')} END;`
340
+ )
341
+ .join('\n');
342
+ const ALL = ['INSERT', 'UPDATE', 'DELETE'];
343
+ // Rows that change what a route/session means (price repairs rewrite route
344
+ // signatures) can touch any day, so they bump the global revision ('').
345
+ const GLOBAL_BUMP = BUMP("''");
346
+
347
+ /**
348
+ * What a completed day's rollup is a function of is written only through
349
+ * these tables, so SQLite triggers (in the writer's own transaction, for
350
+ * every connection and process) bump that day's revision on any insert,
351
+ * update or delete. A route or session row changing bumps the global
352
+ * revision instead, invalidating every held day.
353
+ */
354
+ export const DAY_REVISION_SCHEMA = `
355
+ CREATE TABLE IF NOT EXISTS day_revisions (day TEXT PRIMARY KEY, rev INTEGER NOT NULL) WITHOUT ROWID;
356
+ ${triggers('day_rev_events', 'usage_events', ALL, DAY_KEY)}
357
+ ${triggers('day_rev_daily', 'daily', ALL, (row) => `${row}.day`)}
358
+ ${triggers('day_rev_sessions', 'day_sessions', ALL, (row) => `${row}.day`)}
359
+ ${triggers('day_rev_legacy', 'legacy_days', ALL, (row) => `${row}.day`)}
360
+ CREATE TRIGGER IF NOT EXISTS day_rev_routes_update AFTER UPDATE ON usage_routes BEGIN ${GLOBAL_BUMP} END;
361
+ CREATE TRIGGER IF NOT EXISTS day_rev_routes_delete AFTER DELETE ON usage_routes BEGIN ${GLOBAL_BUMP} END;
362
+ CREATE TRIGGER IF NOT EXISTS day_rev_session_names_update AFTER UPDATE ON usage_sessions BEGIN ${GLOBAL_BUMP} END;
363
+ CREATE TRIGGER IF NOT EXISTS day_rev_session_names_delete AFTER DELETE ON usage_sessions BEGIN ${GLOBAL_BUMP} END;
364
+ `;
365
+
366
+ // In-window days (those with any stored rollup input) with their revisions.
367
+ function readDayFingerprints(db, fromDay, toDay) {
368
+ const global = db.prepare("SELECT rev FROM day_revisions WHERE day=''").get()?.rev ?? 0;
369
+ const revisions = new Map(
370
+ db
371
+ .prepare("SELECT day,rev FROM day_revisions WHERE day<>'' AND day BETWEEN ? AND ?")
372
+ .all(fromDay, toDay)
373
+ .map((row) => [row.day, row.rev])
374
+ );
375
+ const days = db
376
+ .prepare(
377
+ `SELECT day FROM daily WHERE day BETWEEN ?1 AND ?2 UNION SELECT day FROM day_sessions WHERE day BETWEEN ?1 AND ?2
378
+ UNION SELECT day FROM legacy_days WHERE day BETWEEN ?1 AND ?2`
379
+ )
380
+ .all(fromDay, toDay);
381
+ return Object.fromEntries(days.map(({ day }) => [day, `${global}:${revisions.get(day) ?? 0}`]));
382
+ }
383
+
384
+ const DELTA_DAY_QUERIES = 8;
385
+
386
+ /**
387
+ * The part of a whole-day rollup that `known` (day -> fingerprint of the
388
+ * result the caller already holds) cannot answer: every day that is today or
389
+ * later, the hourly day, and any day whose fingerprint moved. Returns every
390
+ * in-window fingerprint plus the recomputed days; assembleRollup() merges
391
+ * them with the caller's held days into exactly rollupUsage()'s answer. A
392
+ * rolling (`fromMs`) window has no reusable day totals and is not served here.
393
+ */
394
+ export function rollupDelta(db, options = {}, known = {}, now = Date.now()) {
395
+ const { hourlyDay = null, fromDay = '0000-01-01', toDay = '9999-12-31' } = options;
396
+ const fingerprints = readDayFingerprints(db, fromDay, toDay);
397
+ const today = usageRollupDayKey(now);
398
+ const stale = Object.keys(fingerprints).filter(
399
+ (day) => day === hourlyDay || day >= today || known[day] !== fingerprints[day]
400
+ );
401
+ if (stale.length > DELTA_DAY_QUERIES) {
402
+ const full = rollupUsage(db, { hourlyDay, fromDay, toDay });
403
+ const fresh = Object.fromEntries(Object.keys(fingerprints).map((day) => [day, full.days[day] ?? null]));
404
+ return { fingerprints, fresh, ...(full.hourly ? { hourly: full.hourly } : {}) };
405
+ }
406
+ const fresh = {};
407
+ let hourly = hourlyDay ? { rows: [], unallocated: [] } : null;
408
+ for (const day of stale) {
409
+ const part = rollupUsage(db, { fromDay: day, toDay: day, hourlyDay: day === hourlyDay ? day : null });
410
+ fresh[day] = part.days[day] ?? null;
411
+ if (part.hourly) hourly = part.hourly;
412
+ }
413
+ return { fingerprints, fresh, ...(hourly ? { hourly } : {}) };
414
+ }
415
+
416
+ /** `known` argument for rollupDelta from the caller's completed-day store. */
417
+ export function knownRollupDays(store) {
418
+ return Object.fromEntries([...store].map(([day, entry]) => [day, entry.fingerprint]));
419
+ }
420
+
421
+ /** Merge a rollupDelta() answer with the held completed days (day -> {fingerprint,value}). */
422
+ export function assembleRollup(store, delta, now = Date.now()) {
423
+ const today = usageRollupDayKey(now);
424
+ const days = {};
425
+ for (const day of Object.keys(delta.fingerprints).sort()) {
426
+ let value;
427
+ if (day in delta.fresh) {
428
+ value = delta.fresh[day];
429
+ if (day < today) store.set(day, { fingerprint: delta.fingerprints[day], value });
430
+ } else value = store.get(day).value;
431
+ if (value) days[day] = value;
432
+ }
433
+ return delta.hourly ? { days, hourly: delta.hourly } : { days };
434
+ }
435
+
332
436
  /** Read cached amounts; group retained attribution separately for distinct sessions. */
333
437
  export function rollupUsage(
334
438
  db,
@@ -7,7 +7,7 @@
7
7
  import { DatabaseSync } from 'node:sqlite';
8
8
  import { serveWorkerRequests } from '../worker-requests.mjs';
9
9
  import { UsageLedger } from './usage-ledger.mjs';
10
- import { readSessionUsage, rollupUsage } from './usage-ledger-rollup.mjs';
10
+ import { readSessionUsage, rollupDelta, rollupUsage } from './usage-ledger-rollup.mjs';
11
11
  import { readQuotaHistory, readQuotaWindows } from './usage-ledger-quota.mjs';
12
12
  import { applyUsageRepairIfUnchanged, selectUsageRepairCandidates } from './usage-ledger-repair.mjs';
13
13
  import { unpricedCountAfter, unpricedPendingCounts } from './usage-pricing-refresh.mjs';
@@ -59,6 +59,7 @@ function recordBatch(ledger, rows) {
59
59
 
60
60
  const operations = {
61
61
  rollup: ({ path, options }) => read(path, (db) => rollupUsage(db, options)),
62
+ rollupDelta: ({ path, options, known, now }) => read(path, (db) => rollupDelta(db, options, known, now)),
62
63
  sessionUsage: ({ path, sessionIds }) => read(path, (db) => readSessionUsage(db, sessionIds)),
63
64
  record: ({ path, rows }) => write(path, (ledger) => recordBatch(ledger, rows)),
64
65
  recordQuota: ({ path, samples }) => write(path, (ledger) => ledger.recordQuota(samples)),
@@ -11,7 +11,14 @@ import { createHash, randomUUID } from 'node:crypto';
11
11
  import { resolvePluginData } from '../plugin-paths.mjs';
12
12
  import { usageRollupDayKey, isConversationUsageSource, num } from './usage-rollup.mjs';
13
13
  import { priceUsage } from './cost.mjs';
14
- import { readSessionUsage, rollupUsage } from './usage-ledger-rollup.mjs';
14
+ import {
15
+ DAY_REVISION_SCHEMA,
16
+ assembleRollup,
17
+ knownRollupDays,
18
+ readSessionUsage,
19
+ rollupDelta,
20
+ rollupUsage,
21
+ } from './usage-ledger-rollup.mjs';
15
22
  import { QUOTA_SCHEMA, readQuotaHistory, readQuotaWindows, recordQuotaSamples } from './usage-ledger-quota.mjs';
16
23
  import { SHARE_ENV } from 'node:worker_threads';
17
24
  import { createWorkerRequestClient } from '../worker-requests.mjs';
@@ -37,6 +44,7 @@ export function usageLedgerWorkerRunning() {
37
44
  }
38
45
  const stores = new Map();
39
46
  const ROLLUP_CACHE_LIMIT = 8;
47
+ const windowShape = (options) => JSON.stringify({ ...options, fromMs: undefined, toMs: undefined });
40
48
  const text = (value) => (typeof value === 'string' ? value.slice(0, 300) : '');
41
49
 
42
50
  // Keep the full identifier, not a shortened hash. The tag also makes arbitrary
@@ -295,6 +303,10 @@ export class UsageLedger {
295
303
  this.db.exec('PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA foreign_keys=ON;');
296
304
  this.rollupStamp = null;
297
305
  this.rollups = new Map();
306
+ // Completed days' rollup results, validated by day revision (trigger-maintained) on every use,
307
+ // so they survive stamp changes. Rolling-window results by query shape.
308
+ this.dayRollups = new Map();
309
+ this.windowRollups = new Map();
298
310
  this.pendingRollups = new Map();
299
311
  this.writeQueue = [];
300
312
  this.writing = null;
@@ -340,6 +352,7 @@ export class UsageLedger {
340
352
  );
341
353
  CREATE TABLE IF NOT EXISTS legacy_days (day TEXT PRIMARY KEY, document TEXT NOT NULL);
342
354
  ${QUOTA_SCHEMA}
355
+ ${DAY_REVISION_SCHEMA}
343
356
  PRAGMA user_version=2;
344
357
  `);
345
358
  this.prepareStatements();
@@ -575,13 +588,52 @@ export class UsageLedger {
575
588
  * shared between callers and must be treated as read-only.
576
589
  */
577
590
  rollup(options = {}) {
578
- this.syncRollupStamp();
591
+ const stamp = this.syncRollupStamp();
579
592
  const key = JSON.stringify(options);
580
- const result = this.rollups.get(key) ?? rollupUsage(this.db, options);
593
+ const result = this.rollups.get(key) ?? this.reusableWindow(options, stamp) ?? this.computeRollup(options, stamp);
581
594
  this.rememberRollup(key, result);
582
595
  return result;
583
596
  }
584
597
 
598
+ /**
599
+ * A rolling window re-asked under an unchanged ledger is the same answer as
600
+ * long as no record sits in the part of the window that moved (aged out at
601
+ * the start, or newly reachable at the end): one indexed probe instead of
602
+ * a rebuild. Windows of the same shape are keyed without their edges.
603
+ */
604
+ reusableWindow(options, stamp) {
605
+ if (options.fromMs == null || options.toMs == null) return null;
606
+ const held = this.windowRollups.get(windowShape(options));
607
+ if (!held || held.stamp !== stamp) return null;
608
+ const moved = this.db
609
+ .prepare('SELECT 1 FROM usage_events WHERE (ts>=? AND ts<?) OR (ts>? AND ts<=?) LIMIT 1')
610
+ .get(
611
+ Math.min(held.fromMs, options.fromMs),
612
+ Math.max(held.fromMs, options.fromMs),
613
+ Math.min(held.toMs, options.toMs),
614
+ Math.max(held.toMs, options.toMs)
615
+ );
616
+ return moved ? null : held.result;
617
+ }
618
+
619
+ rememberWindow(options, stamp, result) {
620
+ if (options.fromMs == null || options.toMs == null) return;
621
+ const shape = windowShape(options);
622
+ this.windowRollups.delete(shape);
623
+ this.windowRollups.set(shape, { stamp, fromMs: options.fromMs, toMs: options.toMs, result });
624
+ if (this.windowRollups.size > ROLLUP_CACHE_LIMIT) this.windowRollups.delete(this.windowRollups.keys().next().value);
625
+ }
626
+
627
+ computeRollup(options, stamp) {
628
+ if (options.fromMs != null) {
629
+ const result = rollupUsage(this.db, options);
630
+ this.rememberWindow(options, stamp, result);
631
+ return result;
632
+ }
633
+ const now = Date.now();
634
+ return assembleRollup(this.dayRollups, rollupDelta(this.db, options, knownRollupDays(this.dayRollups), now), now);
635
+ }
636
+
585
637
  /**
586
638
  * rollup() with the query run on the shared rollup worker's own read-only
587
639
  * connection, so a changed ledger never costs the event loop the full
@@ -598,14 +650,29 @@ export class UsageLedger {
598
650
  this.rememberRollup(key, cached);
599
651
  return cached;
600
652
  }
653
+ const reused = this.reusableWindow(options, stamp);
654
+ if (reused) {
655
+ this.rememberRollup(key, reused);
656
+ return reused;
657
+ }
601
658
  let pending = this.pendingRollups.get(key);
602
659
  if (!pending) {
603
- pending = this.workerRequest('rollup', { options })
660
+ const now = Date.now();
661
+ const request =
662
+ options.fromMs != null
663
+ ? this.workerRequest('rollup', { options })
664
+ : this.workerRequest('rollupDelta', { options, known: knownRollupDays(this.dayRollups), now }).then((delta) =>
665
+ assembleRollup(this.dayRollups, delta, now)
666
+ );
667
+ pending = request
604
668
  .then((result) => {
605
669
  // A newer stamp cleared the cache meanwhile; never file this
606
670
  // answer under it. The worker may have read commits newer than
607
671
  // `stamp`, which only means the next stamp change discards it.
608
- if (this.rollupStamp === stamp) this.rememberRollup(key, result);
672
+ if (this.rollupStamp === stamp) {
673
+ this.rememberRollup(key, result);
674
+ this.rememberWindow(options, stamp, result);
675
+ }
609
676
  return result;
610
677
  })
611
678
  .finally(() => {
@@ -0,0 +1,6 @@
1
+ // onnxruntime-node ships no macOS Intel (darwin-x64) native binding since
2
+ // 1.23, so the features built on it (memory embeddings, the Auto effort
3
+ // judge) are unavailable there; everything else runs normally.
4
+ export function onnxRuntimeSupported(platform = process.platform, arch = process.arch) {
5
+ return !(platform === 'darwin' && arch === 'x64');
6
+ }
@@ -1,18 +1,26 @@
1
1
  // Orchestration is independent of workflow instructions and agent definitions.
2
2
  export const ORCHESTRATION_MODES = Object.freeze(['none', 'focused', 'balanced', 'swarm']);
3
3
 
4
+ export const DEFAULT_ORCHESTRATION_MODE = 'balanced';
5
+
4
6
  export function normalizeOrchestrationMode(value, fallback = 'none') {
5
7
  return ORCHESTRATION_MODES.includes(value) ? value : fallback;
6
8
  }
7
9
 
10
+ // Before the setting existed, Solo/Headless disabled delegation; Cowork (the
11
+ // old `default` pack) and custom workflows enabled it. Solo was the shipped
12
+ // default workflow then, so only a non-Solo choice is an explicit user choice.
13
+ // Used for sessions recorded without a mode and by the one-time file
14
+ // separation; the live config never infers a mode from the workflow.
15
+ export function legacyWorkflowOrchestrationMode(active) {
16
+ return active && active !== 'solo' && active !== 'headless' ? 'swarm' : 'none';
17
+ }
18
+
8
19
  export function configuredOrchestrationMode(config) {
9
20
  if (Object.hasOwn(config || {}, 'orchestrationMode')) {
10
- return normalizeOrchestrationMode(config.orchestrationMode);
21
+ return normalizeOrchestrationMode(config.orchestrationMode, DEFAULT_ORCHESTRATION_MODE);
11
22
  }
12
- // Before this setting existed, Solo/Headless disabled delegation; Cowork
13
- // (the old `default` pack) and custom workflows enabled it.
14
- const legacy = config?.workflow?.active;
15
- return legacy && legacy !== 'solo' && legacy !== 'headless' ? 'swarm' : 'none';
23
+ return DEFAULT_ORCHESTRATION_MODE;
16
24
  }
17
25
 
18
26
  export function normalizeWorkflowSelection(config) {
@@ -31,5 +39,5 @@ export function sessionOrchestrationMode(session) {
31
39
  if (workflow?.delegatesAgents === false || (workflow?.agentsConfigured === true && workflow?.agents?.length === 0))
32
40
  return 'none';
33
41
  if (workflow?.delegatesAgents === true) return 'swarm';
34
- return workflow ? configuredOrchestrationMode({ workflow: { active: workflow.id } }) : 'swarm';
42
+ return workflow ? legacyWorkflowOrchestrationMode(workflow.id) : 'swarm';
35
43
  }
@@ -36,11 +36,16 @@
36
36
  "MIXDOG_STALL_FIRST_BYTE_ABORT_S",
37
37
  "MIXDOG_CACHE_MESSAGES_TTL",
38
38
  "MIXDOG_OAI_TRANSPORT",
39
- "MIXDOG_OPENAI_OAUTH_WS_WARMUP",
40
39
  "MIXDOG_OAI_WS_DUMP_DIR",
41
40
  "MIXDOG_SESSION_TRANSCRIPT_LOG",
42
41
  "MIXDOG_SEARCH_SERVER_BIN",
43
- "MIXDOG_USAGE_LOG"
42
+ "MIXDOG_USAGE_LOG",
43
+ "MIXDOG_AUTO_EFFORT",
44
+ "MIXDOG_AUTO_EFFORT_STEPS",
45
+ "MIXDOG_EFFORT_JUDGE_DIR",
46
+ "MIXDOG_EFFORT_DECISIONS_LOG",
47
+ "MIXDOG_EFFORT_JUDGE_COLD_WAIT_MS",
48
+ "MIXDOG_EFFORT_JUDGE_TIMEOUT_MS"
44
49
  ],
45
50
  "oauthProviders": {
46
51
  "anthropic-oauth": {
@@ -9,7 +9,8 @@
9
9
  */
10
10
  import { homedir } from 'node:os';
11
11
  import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
12
- import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
12
+ import { basename, isAbsolute, join, resolve } from 'node:path';
13
+ import { withFileLockSync, writeJsonAtomicSync } from './atomic-file.mjs';
13
14
 
14
15
  const MIXDOG_HOME = process.env.MIXDOG_HOME || join(homedir(), '.mixdog');
15
16
  const PROJECTS_FILE = process.env.MIXDOG_PROJECTS_FILE
@@ -29,11 +30,19 @@ function normalizeKey(absPath) {
29
30
  return process.platform === 'win32' ? text.replace(/[\\/]+$/, '').toLowerCase() : text.replace(/\/+$/, '');
30
31
  }
31
32
 
32
- function readStore() {
33
+ function readStore(strict = false) {
33
34
  try {
34
- if (!existsSync(PROJECTS_FILE)) return { projects: [] };
35
+ if (!strict && !existsSync(PROJECTS_FILE)) return { projects: [] };
35
36
  const raw = readFileSync(PROJECTS_FILE, 'utf8');
36
37
  const parsed = JSON.parse(raw);
38
+ if (
39
+ strict &&
40
+ (!Array.isArray(parsed?.projects) ||
41
+ parsed.projects.some(
42
+ (entry) => !entry || typeof entry.path !== 'string' || !entry.path.trim() || !isAbsolute(entry.path)
43
+ ))
44
+ )
45
+ throw new TypeError('Project registry is invalid.');
37
46
  const projects = Array.isArray(parsed?.projects) ? parsed.projects : [];
38
47
  return {
39
48
  projects: projects
@@ -45,20 +54,23 @@ function readStore() {
45
54
  ...(Number(entry.lastSelectedAt) > 0 ? { lastSelectedAt: Number(entry.lastSelectedAt) } : {}),
46
55
  })),
47
56
  };
48
- } catch {
57
+ } catch (error) {
58
+ if (strict && error?.code !== 'ENOENT') throw error;
49
59
  return { projects: [] };
50
60
  }
51
61
  }
52
62
 
53
- function writeStore(store) {
54
- try {
55
- mkdirSync(dirname(PROJECTS_FILE), { recursive: true });
56
- const tmp = `${PROJECTS_FILE}.${process.pid}.tmp`;
57
- writeFileSync(tmp, `${JSON.stringify(store, null, 2)}\n`, 'utf8');
58
- renameSync(tmp, PROJECTS_FILE);
59
- } catch {
60
- /* best-effort persistence */
61
- }
63
+ // Serialize the entire read/modify/write across cooperating processes, not
64
+ // just publication. Unlike the generic JSON updater, a corrupt registry must
65
+ // never be repaired into an empty document. Preserve synchronous return values
66
+ // and no-op behavior; failed reads, lock acquisition and writes propagate.
67
+ function mutateStore(mutator) {
68
+ return withFileLockSync(`${PROJECTS_FILE}.lock`, () => {
69
+ const store = readStore(true);
70
+ const { value, changed } = mutator(store);
71
+ if (changed) writeJsonAtomicSync(PROJECTS_FILE, store, { lock: false });
72
+ return value;
73
+ });
62
74
  }
63
75
 
64
76
  /**
@@ -118,6 +130,13 @@ export function listProjects() {
118
130
  return projects.slice().sort(compareProjects);
119
131
  }
120
132
 
133
+ /** Permission checks must not mistake unreadable/corrupt storage for removal.
134
+ * Mutations also use strict reads; only ordinary listing remains tolerant. */
135
+ export function listProjectsStrict() {
136
+ const { projects } = readStore(true);
137
+ return projects.slice().sort(compareProjects);
138
+ }
139
+
121
140
  /**
122
141
  * Register a project by path. Derives the display name from the last path
123
142
  * segment, dedupes by normalized absolute path, and persists. Does NOT create
@@ -126,20 +145,16 @@ export function listProjects() {
126
145
  export function addProject(rawPath) {
127
146
  const absPath = toAbsolute(rawPath);
128
147
  if (!absPath) return null;
129
- const store = readStore();
130
- const key = normalizeKey(absPath);
131
- const existing = store.projects.find((entry) => normalizeKey(entry.path) === key);
132
- if (existing) {
133
- ensureProjectIdMarker(absPath, existing.name);
134
- return existing;
135
- }
136
- const entry = {
137
- name: basename(absPath) || absPath,
138
- path: absPath,
139
- addedAt: Date.now(),
140
- };
141
- store.projects.push(entry);
142
- writeStore(store);
148
+ const entry = mutateStore((store) => {
149
+ const key = normalizeKey(absPath);
150
+ const existing = store.projects.find((item) => normalizeKey(item.path) === key);
151
+ if (existing) return { value: existing, changed: false };
152
+ const added = { name: basename(absPath) || absPath, path: absPath, addedAt: Date.now() };
153
+ store.projects.push(added);
154
+ return { value: added, changed: true };
155
+ });
156
+ // Auxiliary markers stay best-effort and outside the registry's lock. A
157
+ // failed registration must not create a marker that suggests it succeeded.
143
158
  ensureProjectIdMarker(absPath, entry.name);
144
159
  return entry;
145
160
  }
@@ -152,13 +167,14 @@ export function addProject(rawPath) {
152
167
  export function touchProjectSelected(rawPath) {
153
168
  const absPath = toAbsolute(rawPath);
154
169
  if (!absPath) return null;
155
- const store = readStore();
156
- const key = normalizeKey(absPath);
157
- const entry = store.projects.find((item) => normalizeKey(item.path) === key);
158
- if (!entry) return null;
159
- entry.lastSelectedAt = Date.now();
160
- writeStore(store);
161
- ensureProjectIdMarker(absPath, entry.name);
170
+ const entry = mutateStore((store) => {
171
+ const key = normalizeKey(absPath);
172
+ const selected = store.projects.find((item) => normalizeKey(item.path) === key);
173
+ if (!selected) return { value: null, changed: false };
174
+ selected.lastSelectedAt = Date.now();
175
+ return { value: selected, changed: true };
176
+ });
177
+ if (entry) ensureProjectIdMarker(absPath, entry.name);
162
178
  return entry;
163
179
  }
164
180
 
@@ -166,12 +182,13 @@ export function touchProjectSelected(rawPath) {
166
182
  export function removeProject(rawPath) {
167
183
  const absPath = toAbsolute(rawPath);
168
184
  if (!absPath) return false;
169
- const store = readStore();
170
- const key = normalizeKey(absPath);
171
- const next = store.projects.filter((entry) => normalizeKey(entry.path) !== key);
172
- if (next.length === store.projects.length) return false;
173
- writeStore({ projects: next });
174
- return true;
185
+ return mutateStore((store) => {
186
+ const key = normalizeKey(absPath);
187
+ const next = store.projects.filter((entry) => normalizeKey(entry.path) !== key);
188
+ if (next.length === store.projects.length) return { value: false, changed: false };
189
+ store.projects = next;
190
+ return { value: true, changed: true };
191
+ });
175
192
  }
176
193
 
177
194
  /**
@@ -182,14 +199,14 @@ export function removeProject(rawPath) {
182
199
  export function renameProject(rawPath, nextName) {
183
200
  const absPath = toAbsolute(rawPath);
184
201
  if (!absPath) return null;
185
- const store = readStore();
186
- const key = normalizeKey(absPath);
187
- const entry = store.projects.find((item) => normalizeKey(item.path) === key);
188
- if (!entry) return null;
189
- const trimmed = String(nextName || '').trim();
190
- entry.name = trimmed || basename(absPath) || absPath;
191
- writeStore(store);
192
- return entry;
202
+ return mutateStore((store) => {
203
+ const key = normalizeKey(absPath);
204
+ const entry = store.projects.find((item) => normalizeKey(item.path) === key);
205
+ if (!entry) return { value: null, changed: false };
206
+ const trimmed = String(nextName || '').trim();
207
+ entry.name = trimmed || basename(absPath) || absPath;
208
+ return { value: entry, changed: true };
209
+ });
193
210
  }
194
211
 
195
212
  /** Whether a path currently exists on disk. */