mixdog 1.2.6 → 1.3.1

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 (294) hide show
  1. package/LICENSES/simple-icons-CC0-1.0.txt +30 -0
  2. package/NOTICE.md +71 -9
  3. package/package.json +4 -1
  4. package/scripts/check-model-recommendations.mjs +89 -0
  5. package/scripts/gate-local.mjs +7 -0
  6. package/scripts/lib/isolated-test-env.mjs +16 -0
  7. package/src/agents/reviewer/AGENT.md +4 -2
  8. package/src/agents/reviewer/agent.json +1 -1
  9. package/src/defaults/shipped-history.json +10 -0
  10. package/src/defaults/skills/browser-use/SKILL.md +5 -0
  11. package/src/defaults/skills/memory-management/SKILL.md +4 -1
  12. package/src/defaults/skills/setup/references/actions.md +14 -4
  13. package/src/defaults/skills/setup-guide/SKILL.md +178 -0
  14. package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
  15. package/src/lib/rules-builder.cjs +55 -6
  16. package/src/output-styles/common.md +13 -8
  17. package/src/rules/agent/AGENT.md +4 -0
  18. package/src/rules/agent-mode/AGENT_MODE.md +24 -0
  19. package/src/rules/agent-mode/MIXY.md +28 -0
  20. package/src/rules/agent-mode/ROUTINE.md +6 -0
  21. package/src/rules/lead/LEAD.md +7 -3
  22. package/src/rules/shared/70-delivery.md +3 -0
  23. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
  24. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
  25. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +241 -14
  26. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
  27. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +216 -0
  28. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -4
  29. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
  30. package/src/runtime/agent/orchestrator/mcp/client.mjs +115 -11
  31. package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
  32. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
  33. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
  34. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
  35. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
  36. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
  37. package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +1 -0
  40. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
  41. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +10 -3
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
  43. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +7 -8
  44. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +3 -1
  46. package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
  47. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
  48. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +9 -2
  49. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
  50. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
  51. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
  52. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
  53. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
  54. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +96 -1
  55. package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
  56. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
  57. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +14 -0
  58. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
  59. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
  60. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
  61. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +21 -5
  62. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +40 -7
  63. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +52 -20
  64. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
  65. package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
  66. package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
  67. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
  68. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
  69. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
  70. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +166 -0
  71. package/src/runtime/agent/orchestrator/session/agent-room.mjs +650 -0
  72. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
  73. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
  75. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +192 -0
  76. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
  77. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
  78. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
  79. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
  80. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
  81. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
  82. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
  83. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +108 -6
  84. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -1
  85. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +74 -15
  86. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +9 -1
  87. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
  88. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
  89. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +49 -0
  90. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
  91. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
  92. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
  93. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  94. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
  95. package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
  96. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
  97. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
  98. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
  99. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
  100. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +33 -24
  101. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +58 -8
  102. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
  103. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
  104. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
  105. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
  106. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +27 -0
  108. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
  109. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +86 -0
  110. package/src/runtime/agent/orchestrator/session/routine-result.mjs +329 -0
  111. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
  112. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +28 -9
  113. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
  114. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
  115. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
  116. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
  117. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +58 -10
  118. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +92 -15
  119. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
  121. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +4 -0
  122. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +25 -1
  123. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +8 -0
  124. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +6 -0
  125. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -0
  126. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +75 -57
  127. package/src/runtime/attachments/store.mjs +6 -0
  128. package/src/runtime/browser-bridge/client.mjs +24 -0
  129. package/src/runtime/channels/lib/scheduler.mjs +40 -12
  130. package/src/runtime/channels/lib/schedules-db.mjs +71 -12
  131. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
  132. package/src/runtime/channels/lib/webhook.mjs +0 -1
  133. package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
  134. package/src/runtime/channels/lib/worker-main.mjs +17 -20
  135. package/src/runtime/computer-bridge/client.mjs +28 -1
  136. package/src/runtime/connectors/gmail-app-password.mjs +690 -0
  137. package/src/runtime/connectors/google-mcp.mjs +446 -0
  138. package/src/runtime/effort-judge/judge-client.mjs +1 -1
  139. package/src/runtime/effort-judge/model-manifest.json +7 -7
  140. package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
  141. package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
  142. package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
  143. package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
  144. package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
  145. package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
  146. package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
  147. package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
  148. package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
  149. package/src/runtime/memory/lib/memory.mjs +7 -1
  150. package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
  151. package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
  152. package/src/runtime/memory/lib/tool-call-handler.mjs +2 -0
  153. package/src/runtime/memory/tool-defs.mjs +5 -0
  154. package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
  155. package/src/runtime/shared/agent-avatar.d.mts +64 -0
  156. package/src/runtime/shared/agent-avatar.mjs +320 -0
  157. package/src/runtime/shared/agent-kind.mjs +152 -0
  158. package/src/runtime/shared/agent-route-config.d.mts +4 -0
  159. package/src/runtime/shared/agent-route-config.mjs +30 -0
  160. package/src/runtime/shared/agent-store.mjs +132 -0
  161. package/src/runtime/shared/atomic-file.mjs +14 -1
  162. package/src/runtime/shared/bot-split-migration.mjs +457 -0
  163. package/src/runtime/shared/config-patch.mjs +20 -1
  164. package/src/runtime/shared/config.mjs +8 -1
  165. package/src/runtime/shared/connector-accounts.mjs +15 -0
  166. package/src/runtime/shared/connector-catalog.d.mts +51 -0
  167. package/src/runtime/shared/connector-catalog.mjs +228 -0
  168. package/src/runtime/shared/connector-triggers.d.mts +3 -0
  169. package/src/runtime/shared/connector-triggers.mjs +45 -0
  170. package/src/runtime/shared/err-text.mjs +3 -1
  171. package/src/runtime/shared/error-presentation.mjs +91 -6
  172. package/src/runtime/shared/google-oauth-client.mjs +24 -0
  173. package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
  174. package/src/runtime/shared/handoff-artifacts.mjs +61 -0
  175. package/src/runtime/shared/held-request-host.d.mts +12 -0
  176. package/src/runtime/shared/held-request-host.mjs +71 -0
  177. package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
  178. package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
  179. package/src/runtime/shared/model-route-fallback.mjs +79 -7
  180. package/src/runtime/shared/plain-preview.d.mts +1 -0
  181. package/src/runtime/shared/plain-preview.mjs +33 -0
  182. package/src/runtime/shared/routine-limits.d.mts +11 -0
  183. package/src/runtime/shared/routine-limits.mjs +21 -0
  184. package/src/runtime/shared/routine-trigger.d.mts +10 -0
  185. package/src/runtime/shared/routine-trigger.mjs +44 -0
  186. package/src/runtime/shared/schedule-model-ref.mjs +9 -10
  187. package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
  188. package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
  189. package/src/runtime/shared/slow-span.mjs +4 -12
  190. package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
  191. package/src/runtime/shared/transcript-metadata.mjs +10 -0
  192. package/src/runtime/shared/turn-changes.mjs +0 -8
  193. package/src/runtime/shared/usage-readout-totals.mjs +58 -0
  194. package/src/runtime/shared/user-data-guard.mjs +119 -38
  195. package/src/session-runtime/agent-home-delivery.mjs +826 -0
  196. package/src/session-runtime/agent-profile-changes.mjs +21 -0
  197. package/src/session-runtime/agent-room-delivery.mjs +133 -0
  198. package/src/session-runtime/agent-task-sessions.mjs +460 -0
  199. package/src/session-runtime/boot/apis.mjs +63 -18
  200. package/src/session-runtime/boot/facade.mjs +5 -8
  201. package/src/session-runtime/boot/infrastructure.mjs +28 -17
  202. package/src/session-runtime/boot/providers.mjs +14 -4
  203. package/src/session-runtime/boot/tools.mjs +3 -0
  204. package/src/session-runtime/channel-config-api.mjs +53 -5
  205. package/src/session-runtime/config-lifecycle.mjs +43 -2
  206. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
  207. package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
  208. package/src/session-runtime/handoff-attachments.mjs +593 -0
  209. package/src/session-runtime/held-requests.mjs +228 -0
  210. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
  211. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
  212. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
  213. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +56 -3
  214. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  215. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
  216. package/src/session-runtime/lifecycle/session-switch/resume.mjs +4 -0
  217. package/src/session-runtime/lifecycle/shared.mjs +6 -0
  218. package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
  219. package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
  220. package/src/session-runtime/mcp-oauth-login.mjs +112 -15
  221. package/src/session-runtime/model-route/route-persist.mjs +2 -2
  222. package/src/session-runtime/model-route/route-tuning.mjs +7 -1
  223. package/src/session-runtime/model-route/set-route.mjs +35 -4
  224. package/src/session-runtime/native-web-search.mjs +2 -2
  225. package/src/session-runtime/new-session-config.mjs +6 -1
  226. package/src/session-runtime/owned-automation-run.mjs +229 -0
  227. package/src/session-runtime/resource-api.mjs +5 -0
  228. package/src/session-runtime/resource-mcp-api.mjs +107 -13
  229. package/src/session-runtime/resource-skills-api.mjs +12 -0
  230. package/src/session-runtime/room-member-gate.mjs +48 -0
  231. package/src/session-runtime/route-resolution.mjs +5 -11
  232. package/src/session-runtime/routine-home-turn.mjs +156 -0
  233. package/src/session-runtime/routine-owner.mjs +48 -0
  234. package/src/session-runtime/schedule-session-run.mjs +30 -64
  235. package/src/session-runtime/services/agent-tool/helpers.mjs +30 -1
  236. package/src/session-runtime/services/agent-tool/send-flow.mjs +11 -3
  237. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
  238. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
  239. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  240. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
  241. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  242. package/src/session-runtime/services/channel-admin.mjs +93 -24
  243. package/src/session-runtime/services/memory-access.mjs +37 -0
  244. package/src/session-runtime/services/provider-admin.mjs +74 -39
  245. package/src/session-runtime/services/schedule-access.mjs +91 -0
  246. package/src/session-runtime/session-create/session-options.mjs +34 -7
  247. package/src/session-runtime/session-hooks.mjs +16 -8
  248. package/src/session-runtime/setup-tool/executor.mjs +79 -6
  249. package/src/session-runtime/setup-tool/extended-actions.mjs +90 -20
  250. package/src/session-runtime/setup-tool/settings-contract.mjs +14 -6
  251. package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
  252. package/src/session-runtime/skills-api.mjs +16 -1
  253. package/src/session-runtime/tool-policy-refresh.mjs +249 -29
  254. package/src/session-runtime/turn/session-ops.mjs +8 -1
  255. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +10 -2
  256. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +14 -0
  257. package/src/session-runtime/webhook-session-run.mjs +49 -67
  258. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
  259. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +650 -25
  260. package/src/session-runtime/workflow-agents-api/agent-route.mjs +67 -7
  261. package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
  262. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
  263. package/src/session-runtime/workflow-agents-api.mjs +4 -1
  264. package/src/standalone/daemon-agent-control.mjs +1 -8
  265. package/src/standalone/daemon-session-control.mjs +21 -4
  266. package/src/standalone/daemon.mjs +31 -5
  267. package/src/standalone/session-protocol.mjs +27 -0
  268. package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
  269. package/src/standalone/session-service/agent-tree.mjs +4 -3
  270. package/src/standalone/session-service/session-calls/session-turns.mjs +28 -0
  271. package/src/standalone/session-service.mjs +19 -0
  272. package/src/tui/app/slash-commands.mjs +20 -0
  273. package/src/tui/dist/index.mjs +234 -95
  274. package/src/tui/session/context-state.mjs +6 -1
  275. package/src/tui/session/notice-surface.mjs +34 -4
  276. package/src/tui/session/queue-helpers.mjs +2 -1
  277. package/src/tui/session/session-api/extensions.mjs +4 -0
  278. package/src/tui/session/session-api/intake/submission.mjs +5 -0
  279. package/src/tui/session/session-api/integrations.mjs +8 -0
  280. package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
  281. package/src/tui/session/session-api/lifecycle.mjs +17 -1
  282. package/src/tui/session/session-api/routes.mjs +61 -5
  283. package/src/tui/session/session-flow/drain.mjs +12 -3
  284. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
  285. package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
  286. package/src/tui/session/session-flow/steering.mjs +58 -24
  287. package/src/tui/session/tool-approval.mjs +26 -5
  288. package/src/tui/session/transcript-intake.mjs +8 -2
  289. package/src/tui/session/transcript-spill.mjs +5 -1
  290. package/src/tui/session/tui-steering-persist.mjs +65 -42
  291. package/src/tui/session/turn-live-call.mjs +64 -0
  292. package/src/tui/session/turn.mjs +34 -8
  293. package/src/session-runtime/automation-agents.mjs +0 -94
  294. package/src/session-runtime/automation-workflow.mjs +0 -59
@@ -0,0 +1,58 @@
1
+ // Session-cumulative figures behind the composer's usage readout, kept on the
2
+ // session record (`session.usageReadout`) so they survive restart, reload and
3
+ // compaction. One fold per model call of the session itself; calls of other
4
+ // sessions (delegated sub-agents have their own records) never enter.
5
+ //
6
+ // - cache hit = cacheRead / (input + cacheRead + cacheWrite) over every call
7
+ // whose prompt was measured. A call that could not measure its prompt
8
+ // (promptKnown === false) has unknown zeros, so it adds nothing.
9
+ // - speed = output tokens over request ms (provider request start → end of the
10
+ // response, the wait for the first token included: the same wall time as the
11
+ // usage ledger's `duration_ms`) of every call with positive output and
12
+ // positive duration: the same eligibility as the usage ledger and the usage
13
+ // dialog, so the readout and the dialog agree. Records saved before this
14
+ // definition carry `speedOutputTokens` / `speedMs` (first visible token →
15
+ // end); they are kept as they are and read only while no request-timed call
16
+ // has landed.
17
+ // - total tokens = input + cacheRead + cacheWrite + output, from the same sums.
18
+
19
+ function num(value) {
20
+ const n = Number(value);
21
+ return Number.isFinite(n) && n > 0 ? n : 0;
22
+ }
23
+
24
+ /** The zeroed totals record. */
25
+ export function emptyUsageReadout() {
26
+ return { input: 0, cacheRead: 0, cacheWrite: 0, output: 0, requestOutputTokens: 0, requestMs: 0 };
27
+ }
28
+
29
+ /**
30
+ * Fold one `onUsageDelta` event (and the call's request ms, 0 when it was
31
+ * not timed) into the totals; returns a new record, never mutates `totals`.
32
+ * Providers differ on whether input counts cache; the prompt total does not,
33
+ * so fresh input is the prompt less its cache read/write.
34
+ */
35
+ export function foldUsageReadout(totals, delta = {}, requestMs = 0) {
36
+ const base = totals || emptyUsageReadout();
37
+ const next = { ...base };
38
+ if (delta.promptKnown !== false) {
39
+ const cacheRead = num(delta.deltaCachedRead);
40
+ const cacheWrite = num(delta.deltaCacheWrite);
41
+ const prompt = num(delta.deltaPrompt) || num(delta.deltaInput) + cacheRead + cacheWrite;
42
+ next.input = num(base.input) + Math.max(0, prompt - cacheRead - cacheWrite);
43
+ next.cacheRead = num(base.cacheRead) + cacheRead;
44
+ next.cacheWrite = num(base.cacheWrite) + cacheWrite;
45
+ }
46
+ const output = num(delta.deltaOutput);
47
+ // Every call's output enters the session's total tokens, timed or not. A
48
+ // record saved before the field existed has no output sum to extend, and a
49
+ // partial one would pass for the whole: it stays without until the session
50
+ // starts over.
51
+ if (Number.isFinite(base.output)) next.output = base.output + output;
52
+ const ms = num(requestMs);
53
+ if (ms > 0 && output > 0) {
54
+ next.requestOutputTokens = num(base.requestOutputTokens) + output;
55
+ next.requestMs = num(base.requestMs) + ms;
56
+ }
57
+ return next;
58
+ }
@@ -61,25 +61,92 @@ function scopeDirFor(dataDir) {
61
61
  let lastBackupMs = 0;
62
62
  let backupSeq = 0;
63
63
 
64
- // Claims `<iso-ms>-<10-digit seq>-<reason>` with an exclusive mkdir, so names
65
- // sort by timestamp, then sequence, and an existing backup is never reused
66
- // (EEXIST retries with the next sequence). The clamp keeps a backwards clock
67
- // from reordering this process's backups.
64
+ // `<iso-ms>-<10-digit seq>-<reason>`; an older build's name has no sequence.
65
+ const BACKUP_NAME_RE = /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z-(?:(\d{10})-)?/;
66
+
67
+ // The newest backup order among `names` (time, then sequence). An older
68
+ // build's name sorts after every sequenced name of its millisecond, so its
69
+ // sequence counts as unbounded. Names that are no backup's are ignored.
70
+ function newestBackupOrder(names) {
71
+ let ms = 0;
72
+ let seq = -1;
73
+ for (const name of names) {
74
+ const match = BACKUP_NAME_RE.exec(name);
75
+ if (!match) continue;
76
+ const time = Date.parse(`${match[1]}T${match[2]}:${match[3]}:${match[4]}.${match[5]}Z`);
77
+ if (!Number.isFinite(time)) continue;
78
+ const order = match[6] === undefined ? Number.POSITIVE_INFINITY : Number(match[6]);
79
+ if (time > ms || (time === ms && order > seq)) {
80
+ ms = time;
81
+ seq = order;
82
+ }
83
+ }
84
+ return { ms, seq };
85
+ }
86
+
87
+ // A name that sorts after every backup in `names`, whichever process wrote
88
+ // them and whatever its clock said: the backup a restore picks as the newest
89
+ // is then the one written last, and pruning never takes it.
90
+ function nextBackupName(names, reason) {
91
+ const newest = newestBackupOrder(names);
92
+ lastBackupMs = Math.max(Date.now(), lastBackupMs, newest.ms);
93
+ if (lastBackupMs === newest.ms) {
94
+ if (newest.seq === Number.POSITIVE_INFINITY) lastBackupMs += 1;
95
+ else backupSeq = Math.max(backupSeq, newest.seq + 1);
96
+ }
97
+ const time = new Date(lastBackupMs).toISOString().replace(/[:.]/g, '-');
98
+ return `${time}-${String(backupSeq++).padStart(10, '0')}-${safeReason(reason)}`;
99
+ }
100
+
101
+ // The scope's backup directory names. A reservation lists the scope first and
102
+ // a listing failure throws: an order guessed without it could sort before the
103
+ // newest backup.
104
+ function scopeBackupNames(entries) {
105
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
106
+ }
107
+
108
+ // Claims the next name with an exclusive mkdir (EEXIST retries after it).
109
+ // Returns the claimed dir and the scope's names including it, which pruning
110
+ // reuses instead of listing the scope again.
68
111
  function reserveBackupDir(scopeDir, reason) {
69
112
  mkdirSync(scopeDir, { recursive: true });
113
+ const names = scopeBackupNames(readdirSync(scopeDir, { withFileTypes: true }));
114
+ for (;;) {
115
+ const name = nextBackupName(names, reason);
116
+ names.push(name);
117
+ try {
118
+ mkdirSync(join(scopeDir, name));
119
+ return { backupDir: join(scopeDir, name), names };
120
+ } catch (error) {
121
+ if (error?.code !== 'EEXIST') throw error;
122
+ }
123
+ }
124
+ }
125
+
126
+ async function reserveBackupDirAsync(scopeDir, reason) {
127
+ await mkdirP(scopeDir, { recursive: true });
128
+ const names = scopeBackupNames(await readdirP(scopeDir, { withFileTypes: true }));
70
129
  for (;;) {
71
- lastBackupMs = Math.max(Date.now(), lastBackupMs);
72
- const time = new Date(lastBackupMs).toISOString().replace(/[:.]/g, '-');
73
- const backupDir = join(scopeDir, `${time}-${String(backupSeq++).padStart(10, '0')}-${safeReason(reason)}`);
130
+ const name = nextBackupName(names, reason);
131
+ names.push(name);
74
132
  try {
75
- mkdirSync(backupDir);
76
- return backupDir;
133
+ await mkdirP(join(scopeDir, name));
134
+ return { backupDir: join(scopeDir, name), names };
77
135
  } catch (error) {
78
136
  if (error?.code !== 'EEXIST') throw error;
79
137
  }
80
138
  }
81
139
  }
82
140
 
141
+ // The names past the newest `keep`, never the just-written backup.
142
+ function backupsToPrune(names, own, keep = 40) {
143
+ return [...names]
144
+ .sort()
145
+ .reverse()
146
+ .slice(keep)
147
+ .filter((name) => name !== own);
148
+ }
149
+
83
150
  function safeReason(reason) {
84
151
  return (
85
152
  String(reason || 'snapshot')
@@ -197,14 +264,30 @@ function isStructurallyCompleteMixdogConfigBackup(parsed) {
197
264
  return true;
198
265
  }
199
266
 
200
- /** Backup directory names in `dir`, newest first; empty when unreadable. */
201
- function backupDirsNewestFirst(dir) {
267
+ // A backup that is absent, not a backup, or not JSON is no backup; any other
268
+ // failure (EIO, EACCES, EMFILE…) is transient and must not pass for one.
269
+ function isAbsentBackupError(error) {
270
+ return error instanceof SyntaxError || error?.code === 'ENOENT' || error?.code === 'ENOTDIR';
271
+ }
272
+
273
+ /** Backup directory names in `dir`, newest first; empty when absent, throws on other failures. */
274
+ function listBackupDirsNewestFirst(dir) {
202
275
  try {
203
276
  return readdirSync(dir, { withFileTypes: true })
204
277
  .filter((entry) => entry.isDirectory())
205
278
  .map((entry) => entry.name)
206
279
  .sort()
207
280
  .reverse();
281
+ } catch (error) {
282
+ if (isAbsentBackupError(error)) return [];
283
+ throw error;
284
+ }
285
+ }
286
+
287
+ /** Backup directory names in `dir`, newest first; empty when unreadable. */
288
+ function backupDirsNewestFirst(dir) {
289
+ try {
290
+ return listBackupDirsNewestFirst(dir);
208
291
  } catch {
209
292
  return [];
210
293
  }
@@ -213,7 +296,8 @@ function backupDirsNewestFirst(dir) {
213
296
  /**
214
297
  * A backup is restorable only when its manifest names this data dir and this
215
298
  * incarnation of it. Legacy unscoped backups carry no manifest, so they stay
216
- * on disk but never match.
299
+ * on disk but never match. A transient read failure throws: skipping the
300
+ * backup would restore an older one instead.
217
301
  */
218
302
  function readRestorableConfig(backupDir, dataDirKey, instanceId) {
219
303
  try {
@@ -222,20 +306,21 @@ function readRestorableConfig(backupDir, dataDirKey, instanceId) {
222
306
  if (manifest.instanceId !== instanceId) return null;
223
307
  const parsed = JSON.parse(readFileSync(join(backupDir, 'mixdog-config.json'), 'utf8'));
224
308
  return isStructurallyCompleteMixdogConfigBackup(parsed) ? parsed : null;
225
- } catch {
226
- return null;
309
+ } catch (error) {
310
+ if (isAbsentBackupError(error)) return null;
311
+ throw error;
227
312
  }
228
313
  }
229
314
 
230
315
  function findRestorableConfig(dataDir, instanceId) {
231
316
  const key = normalizeDataDir(dataDir);
232
317
  const scopeDir = scopeDirFor(dataDir);
233
- for (const name of backupDirsNewestFirst(scopeDir)) {
318
+ for (const name of listBackupDirsNewestFirst(scopeDir)) {
234
319
  const parsed = readRestorableConfig(join(scopeDir, name), key, instanceId);
235
320
  if (parsed) return parsed;
236
321
  }
237
322
  const root = getBackupRoot();
238
- for (const name of backupDirsNewestFirst(root)) {
323
+ for (const name of listBackupDirsNewestFirst(root)) {
239
324
  if (name.startsWith(SCOPE_PREFIX)) continue;
240
325
  const parsed = readRestorableConfig(join(root, name), key, instanceId);
241
326
  if (parsed) return parsed;
@@ -245,13 +330,15 @@ function findRestorableConfig(dataDir, instanceId) {
245
330
 
246
331
  // Per-process restore lookup, keyed by data dir. A hit is reused while the
247
332
  // backup root and the marker's instanceId are unchanged; this process's own
248
- // backups invalidate it.
333
+ // backups invalidate it, and a `fresh` lookup (an RMW write, which another
334
+ // process's newer backup may precede) always rescans.
249
335
  const restoreLookupCache = new Map();
250
336
 
251
337
  /**
252
338
  * Newest structurally complete mixdog-config.json backed up from THIS data
253
339
  * dir (skips degenerate single-section snapshots from a prior failed RMW).
254
340
  * Never another data dir's config, and nothing for an uninitialized one.
341
+ * Throws on a transient read failure, which is never cached.
255
342
  */
256
343
  export function loadLatestMixdogConfigFromBackup(dataDir, options) {
257
344
  const instanceId = readInitInstanceId(dataDir);
@@ -259,7 +346,8 @@ export function loadLatestMixdogConfigFromBackup(dataDir, options) {
259
346
  const key = normalizeDataDir(dataDir);
260
347
  const root = getBackupRoot();
261
348
  let hit = restoreLookupCache.get(key);
262
- if (!hit || hit.root !== root || hit.instanceId !== instanceId) {
349
+ if (options?.fresh || !hit || hit.root !== root || hit.instanceId !== instanceId) {
350
+ restoreLookupCache.delete(key);
263
351
  hit = { root, instanceId, config: findRestorableConfig(dataDir, instanceId) };
264
352
  restoreLookupCache.set(key, hit);
265
353
  }
@@ -297,8 +385,8 @@ function copyTree(src, dst, copied) {
297
385
  }
298
386
 
299
387
  /** Prune one data dir's scope only; other scopes and legacy entries stay. */
300
- function pruneBackups(scopeDir, keep = 40) {
301
- for (const name of backupDirsNewestFirst(scopeDir).slice(keep)) {
388
+ function pruneBackups(scopeDir, names, own) {
389
+ for (const name of backupsToPrune(names, own)) {
302
390
  try {
303
391
  rmSync(join(scopeDir, name), { recursive: true, force: true });
304
392
  } catch {}
@@ -364,23 +452,26 @@ function finishBackup(dataDir, backupDir, reason) {
364
452
  legacyEvidenceCache.delete(normalizeDataDir(dataDir));
365
453
  }
366
454
 
367
- export function backupUserData(dataDir, reason = 'snapshot') {
455
+ /** `dirs`: more data-dir folders this one snapshot also carries (a one-time
456
+ * migration's, e.g. agents/ and bots/); routine snapshots keep to
457
+ * USER_DATA_DIRS so the ring stays small. */
458
+ export function backupUserData(dataDir, reason = 'snapshot', { dirs = [] } = {}) {
368
459
  if (skipBackups()) return { dir: null, copied: [] };
369
460
  if (!dataDir || !existsSync(dataDir)) return { dir: null, copied: [] };
370
461
  const scopeDir = scopeDirFor(dataDir);
371
- const backupDir = reserveBackupDir(scopeDir, reason);
462
+ const { backupDir, names } = reserveBackupDir(scopeDir, reason);
372
463
  const copied = [];
373
464
  for (const rel of USER_DATA_FILES) {
374
465
  const src = join(dataDir, rel);
375
466
  if (existsSync(src)) copyTree(src, join(backupDir, rel), copied);
376
467
  }
377
- for (const rel of USER_DATA_DIRS) {
468
+ for (const rel of [...USER_DATA_DIRS, ...dirs]) {
378
469
  const src = join(dataDir, rel);
379
470
  if (existsSync(src)) copyTree(src, join(backupDir, rel), copied);
380
471
  }
381
472
  if (copied.length > 0) {
382
473
  finishBackup(dataDir, backupDir, reason);
383
- pruneBackups(scopeDir);
474
+ pruneBackups(scopeDir, names, basename(backupDir));
384
475
  if (process.env.MIXDOG_SETUP_QUIET !== '1') {
385
476
  process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}\n`);
386
477
  }
@@ -408,18 +499,8 @@ async function copyTreeAsync(src, dst, copied) {
408
499
  copied.push(dst);
409
500
  }
410
501
 
411
- async function pruneBackupsAsync(scopeDir, keep = 40) {
412
- let entries = [];
413
- try {
414
- entries = (await readdirP(scopeDir, { withFileTypes: true }))
415
- .filter((entry) => entry.isDirectory())
416
- .map((entry) => entry.name)
417
- .sort()
418
- .reverse();
419
- } catch {
420
- return;
421
- }
422
- for (const name of entries.slice(keep)) {
502
+ async function pruneBackupsAsync(scopeDir, names, own) {
503
+ for (const name of backupsToPrune(names, own)) {
423
504
  try {
424
505
  await rmP(join(scopeDir, name), { recursive: true, force: true });
425
506
  } catch {}
@@ -430,7 +511,7 @@ export async function backupUserDataAsync(dataDir, reason = 'snapshot') {
430
511
  if (skipBackups()) return { dir: null, copied: [] };
431
512
  if (!dataDir || !existsSync(dataDir)) return { dir: null, copied: [] };
432
513
  const scopeDir = scopeDirFor(dataDir);
433
- const backupDir = reserveBackupDir(scopeDir, reason);
514
+ const { backupDir, names } = await reserveBackupDirAsync(scopeDir, reason);
434
515
  const copied = [];
435
516
  for (const rel of USER_DATA_FILES) {
436
517
  const src = join(dataDir, rel);
@@ -442,7 +523,7 @@ export async function backupUserDataAsync(dataDir, reason = 'snapshot') {
442
523
  }
443
524
  if (copied.length > 0) {
444
525
  finishBackup(dataDir, backupDir, reason);
445
- await pruneBackupsAsync(scopeDir);
526
+ await pruneBackupsAsync(scopeDir, names, basename(backupDir));
446
527
  if (process.env.MIXDOG_SETUP_QUIET !== '1') {
447
528
  process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}\n`);
448
529
  }