mixdog 1.1.2 → 1.2.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 (424) hide show
  1. package/README.md +8 -5
  2. package/package.json +2 -1
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/test-direct.mjs +4 -2
  6. package/src/defaults/shipped-history.json +10 -0
  7. package/src/defaults/skills/pptx/SKILL.md +6 -6
  8. package/src/defaults/skills/pptx/references/review.md +6 -4
  9. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  10. package/src/defaults/skills/setup/references/actions.md +18 -5
  11. package/src/defaults/skills/visualize/SKILL.md +160 -0
  12. package/src/defaults/skills/visualize/references/craft.md +88 -0
  13. package/src/headless-command.mjs +56 -3
  14. package/src/headless-exec.mjs +17 -1
  15. package/src/help.mjs +2 -0
  16. package/src/rules/shared/60-verification.md +3 -1
  17. package/src/rules/shared/70-delivery.md +4 -2
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  21. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  22. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  23. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  24. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +19 -5
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +133 -15
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +55 -8
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +86 -8
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +474 -164
  33. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  34. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  35. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  36. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -12
  41. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  43. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  45. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  49. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  50. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +21 -6
  51. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  52. package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
  53. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  54. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  55. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  56. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  57. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  58. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  59. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  60. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  61. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +82 -0
  62. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +556 -0
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  64. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  65. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  66. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +15 -0
  67. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  68. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +15 -4
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +4 -4
  77. package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -7
  78. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  79. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  80. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -11
  81. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  82. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  83. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +278 -84
  85. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  86. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  87. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  88. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  90. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  91. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  92. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  93. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
  94. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  95. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  96. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  97. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +24 -13
  98. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  99. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  100. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  101. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  102. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  103. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  104. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  105. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  106. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  107. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  108. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  109. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  112. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  113. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  114. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +5 -3
  115. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -4
  116. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +21 -10
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  119. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
  120. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -3
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +22 -12
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +9 -3
  124. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  125. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  126. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +17 -4
  128. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  129. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  130. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  131. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  132. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  133. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  136. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  137. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  138. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +33 -22
  139. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  140. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  141. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  142. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  143. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  144. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +817 -0
  145. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +36 -0
  146. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +23 -12
  147. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  148. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +202 -374
  149. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  150. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  151. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  152. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +48 -9
  153. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +161 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  157. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  158. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  159. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  160. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  161. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  162. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +203 -33
  163. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  164. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  165. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  166. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  167. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -68
  168. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  169. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +20 -3
  170. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  171. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  175. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  176. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  177. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  179. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  180. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  181. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  182. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  183. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  184. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  185. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  186. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  187. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  188. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  189. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  190. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  191. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  192. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  193. package/src/runtime/attachments/store.d.mts +3 -0
  194. package/src/runtime/attachments/store.mjs +38 -0
  195. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  196. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  197. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  198. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  199. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  200. package/src/runtime/memory/lib/memory.mjs +1 -1
  201. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  202. package/src/runtime/shared/atomic-file.mjs +33 -6
  203. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  204. package/src/runtime/shared/code-graph-state.mjs +12 -0
  205. package/src/runtime/shared/defaults-separation.mjs +2 -0
  206. package/src/runtime/shared/extension-scopes.mjs +5 -3
  207. package/src/runtime/shared/file-lock.mjs +37 -11
  208. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  209. package/src/runtime/shared/git-runner.mjs +4 -3
  210. package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
  211. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  212. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  213. package/src/runtime/shared/llm/model-catalog.mjs +240 -130
  214. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  215. package/src/runtime/shared/llm/provider-catalog-registry.mjs +162 -0
  216. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  217. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  218. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  219. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  220. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  221. package/src/runtime/shared/pristine-execution.mjs +28 -4
  222. package/src/runtime/shared/provider-accounts.mjs +125 -21
  223. package/src/runtime/shared/provider-api-key.mjs +33 -1
  224. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  225. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  226. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  227. package/src/runtime/shared/shell-display.mjs +9 -3
  228. package/src/runtime/shared/singleton-owner.mjs +36 -6
  229. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  230. package/src/runtime/shared/skill-mentions.mjs +133 -0
  231. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  232. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  233. package/src/runtime/shared/tool-primitives.mjs +35 -2
  234. package/src/runtime/shared/tool-surface.mjs +7 -0
  235. package/src/runtime/shared/tool-work-units.mjs +14 -0
  236. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  237. package/src/runtime/shared/turn-changes.mjs +791 -0
  238. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  239. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  240. package/src/runtime/shared/turn-worktree-snapshot.mjs +185 -15
  241. package/src/runtime/shared/worker-requests.mjs +15 -2
  242. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  243. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  244. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  245. package/src/runtime/shared/worktrees/git.mjs +247 -0
  246. package/src/runtime/shared/worktrees/include.mjs +480 -0
  247. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  248. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  249. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  250. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  251. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  252. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  253. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  254. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  255. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  256. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  257. package/src/runtime/visualize-bridge/client.mjs +181 -0
  258. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  259. package/src/session-runtime/automation-agents.mjs +8 -2
  260. package/src/session-runtime/boot/apis.mjs +90 -0
  261. package/src/session-runtime/boot/config.mjs +7 -2
  262. package/src/session-runtime/boot/facade.mjs +21 -0
  263. package/src/session-runtime/boot/infrastructure.mjs +30 -18
  264. package/src/session-runtime/boot/session.mjs +1 -0
  265. package/src/session-runtime/boot/tools.mjs +4 -0
  266. package/src/session-runtime/config-lifecycle.mjs +10 -1
  267. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  268. package/src/session-runtime/conversation-import/service.mjs +369 -0
  269. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +21 -6
  270. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  271. package/src/session-runtime/global-extensions.mjs +2 -2
  272. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  273. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  274. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  275. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  276. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  277. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  278. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  279. package/src/session-runtime/mcp-glue/connect.mjs +68 -16
  280. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  281. package/src/session-runtime/mcp-glue/server-catalog.mjs +122 -33
  282. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  283. package/src/session-runtime/mcp-glue.mjs +22 -2
  284. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  285. package/src/session-runtime/model-route/set-route.mjs +15 -2
  286. package/src/session-runtime/new-session-config.mjs +3 -2
  287. package/src/session-runtime/notification-bus.mjs +18 -3
  288. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  289. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  290. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  291. package/src/session-runtime/provider-readiness.mjs +16 -10
  292. package/src/session-runtime/resource-extension-sync.mjs +45 -7
  293. package/src/session-runtime/resource-mcp-api.mjs +121 -84
  294. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  295. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  296. package/src/session-runtime/resource-skills-api.mjs +23 -2
  297. package/src/session-runtime/route-resolution.mjs +10 -1
  298. package/src/session-runtime/route-state.mjs +16 -2
  299. package/src/session-runtime/runtime-core.mjs +5 -0
  300. package/src/session-runtime/runtime-facade.mjs +8 -0
  301. package/src/session-runtime/runtime-review-api.mjs +9 -0
  302. package/src/session-runtime/schedule-session-run.mjs +1 -1
  303. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  304. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  305. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  306. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  307. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  308. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  310. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  311. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  312. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  313. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  314. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  315. package/src/session-runtime/services/agent-tool.mjs +1 -0
  316. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  317. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  318. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  319. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  320. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  321. package/src/session-runtime/services/provider-admin.mjs +64 -18
  322. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  323. package/src/session-runtime/session-create/session-options.mjs +10 -1
  324. package/src/session-runtime/session-turn-api.mjs +1 -0
  325. package/src/session-runtime/settings-api.mjs +4 -2
  326. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  327. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  328. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  329. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  330. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -1
  331. package/src/session-runtime/setup-tool/tool-defs.mjs +38 -4
  332. package/src/session-runtime/skills-api.mjs +7 -3
  333. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  334. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +14 -3
  335. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  336. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  337. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  338. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  339. package/src/session-runtime/turn/turn-run.mjs +6 -7
  340. package/src/session-runtime/webhook-session-run.mjs +16 -4
  341. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  342. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  343. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  344. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  345. package/src/session-runtime/workspace/transitions.mjs +651 -0
  346. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  347. package/src/skill-script-entry.mjs +36 -0
  348. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  349. package/src/standalone/channel-transport.mjs +13 -1
  350. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  351. package/src/standalone/daemon-owner.mjs +372 -0
  352. package/src/standalone/daemon-stored-session-views.mjs +15 -4
  353. package/src/standalone/daemon.mjs +179 -8
  354. package/src/standalone/desktop-service-registry.mjs +20 -0
  355. package/src/standalone/local-session-runtime.mjs +11 -0
  356. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  357. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  358. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  359. package/src/standalone/session-client.mjs +104 -13
  360. package/src/standalone/session-protocol.mjs +21 -0
  361. package/src/standalone/session-proxy.mjs +3 -0
  362. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  363. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  364. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  365. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  366. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  367. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  368. package/src/standalone/session-service/session-calls/session-action.mjs +17 -4
  369. package/src/standalone/session-transport/call-route.mjs +13 -0
  370. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  371. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  372. package/src/standalone/session-transport/client-registry.mjs +20 -3
  373. package/src/standalone/session-transport/routes.mjs +79 -2
  374. package/src/standalone/session-transport/stack.mjs +2 -0
  375. package/src/standalone/session-transport.mjs +24 -1
  376. package/src/tui/App.jsx +9 -1
  377. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  378. package/src/tui/app/app-view.jsx +6 -0
  379. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  380. package/src/tui/app/message-selector.mjs +2 -1
  381. package/src/tui/app/model-options.mjs +37 -5
  382. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  383. package/src/tui/app/model-picker.mjs +4 -2
  384. package/src/tui/app/skill-mention-palette.mjs +88 -0
  385. package/src/tui/app/slash-palette.mjs +23 -7
  386. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  387. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  388. package/src/tui/app/use-prompt-state.mjs +5 -0
  389. package/src/tui/components/Message.jsx +22 -1
  390. package/src/tui/components/PromptInput.jsx +21 -3
  391. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  392. package/src/tui/components/TranscriptItem.jsx +9 -1
  393. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  394. package/src/tui/components/user-message-skills.mjs +38 -0
  395. package/src/tui/dist/index.mjs +839 -207
  396. package/src/tui/index.jsx +10 -2
  397. package/src/tui/session/compaction-view.mjs +147 -0
  398. package/src/tui/session/context-state.mjs +6 -0
  399. package/src/tui/session/initial-state.mjs +3 -0
  400. package/src/tui/session/queue-helpers.mjs +7 -2
  401. package/src/tui/session/quota-resume.mjs +2 -1
  402. package/src/tui/session/session-api/commands.mjs +10 -2
  403. package/src/tui/session/session-api/controls.mjs +4 -0
  404. package/src/tui/session/session-api/extensions.mjs +4 -2
  405. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  406. package/src/tui/session/session-api/integrations.mjs +17 -0
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  408. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  409. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  410. package/src/tui/session/session-api/routes.mjs +10 -1
  411. package/src/tui/session/session-api/settings.mjs +9 -0
  412. package/src/tui/session/session-api/worktree.mjs +60 -0
  413. package/src/tui/session/session-api.mjs +2 -0
  414. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  415. package/src/tui/session/session-flow/drain.mjs +27 -3
  416. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  417. package/src/tui/session/session-flow.mjs +2 -0
  418. package/src/tui/session/transcript-intake.mjs +14 -5
  419. package/src/tui/session/tui-steering-persist.mjs +25 -9
  420. package/src/tui/session/turn-stream.mjs +32 -4
  421. package/src/tui/session/turn-tool-cards.mjs +10 -2
  422. package/src/tui/session/turn.mjs +56 -10
  423. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  424. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
@@ -6,7 +6,6 @@
6
6
  * unless a durable turn checkpoint exists; only then does it lazily enter the
7
7
  * reconnect recovery boundary so restored panes never paint a stale prompt.
8
8
  */
9
- import { readdirSync } from 'node:fs';
10
9
  import { readFile } from 'node:fs/promises';
11
10
  import { join } from 'node:path';
12
11
  // Leaf helpers only (no store.mjs, no workers, no config): the three-way
@@ -14,8 +13,15 @@ import { join } from 'node:path';
14
13
  // authoritative store uses, so the cold catalog cannot disagree with it.
15
14
  import { probePath, readTextFile, PROBE_PRESENT, PROBE_ABSENT } from './store/fs-probe.mjs';
16
15
  import { readTopLevelLifecycleRecord, isLifecycleUnreadable } from './lifecycle-scan.mjs';
17
- import { isAgentOnlySession, isRootLeadSession, sessionVisibility } from './store-summary-visibility.mjs';
18
- import { applySummaryLogText, readSummaryLogText } from './store-summary-log.mjs';
16
+ import { sessionVisibility } from './store-summary-visibility.mjs';
17
+ import {
18
+ STAMP_ABSENT_CODES,
19
+ createOrdinaryCatalog,
20
+ indexListing,
21
+ observeStamp,
22
+ scanSessionFiles,
23
+ stampedDerivation,
24
+ } from './store-summary-catalog.mjs';
19
25
  import { createStoredTranscriptCache, nextProjectionStamp } from './store-transcript-cache.mjs';
20
26
  import {
21
27
  OFFLOAD_MIN_CHARS,
@@ -24,8 +30,11 @@ import {
24
30
  } from './store-transcript-worker.mjs';
25
31
  import { projectStoredTranscript } from './store-transcript-projection.mjs';
26
32
  import { dataDir, sessionHeartbeatMtimes } from './store-summary-locations.mjs';
27
- import { desktopSession, isStoredSessionId, positiveNumber } from './store-summary-fields.mjs';
33
+ import { isStoredSessionId, workspaceSummary } from './store-summary-fields.mjs';
28
34
  import { readArchivedAgentResult } from './store-summary-archived-agent.mjs';
35
+ import { compactionArchivePath, readCompactionArchivePage } from './compaction-archive.mjs';
36
+ import { currentSessionCatalogDb, onSessionCatalogDbChange, sessionCatalogDb } from './catalog-db/index.mjs';
37
+ import { decodeCatalogCursor, encodeCatalogCursor, isAfterCursor } from './catalog-db/schema.mjs';
29
38
  import {
30
39
  readCanonicalSessionRecord as readCanonicalLifecycle,
31
40
  offerStrictSessionRecord,
@@ -64,328 +73,120 @@ export function storedTranscriptCacheStats() {
64
73
  return storedTranscriptCache.stats();
65
74
  }
66
75
 
67
- // ── Stamp-keyed strict derivations ──────────────────────────────────────────
68
- // Session metadata and catalog rows are small values derived from a strict
69
- // parse of the whole record. They are reused only while the file still has
70
- // the FULL stamp { dev, ino, size, mtimeNs, ctimeNs } the parsed bytes were
71
- // proven to come from (stat → read → stat) and that stamp is past the racy
72
- // window. Any other file — changed, replaced by another writer, unreadable —
73
- // is read and strictly parsed again.
74
- const STAMPED_DERIVATION_LIMIT = 2048;
75
- const stampedDerivations = new Map(); // `${kind}|${path}` → { stamp, value }
76
+ // ── Ordinary catalog ────────────────────────────────────────────────────────
77
+ // The JSON path is the catalog engine of store-summary-catalog.mjs on this
78
+ // thread: every process but the daemon, and the daemon whenever its session
79
+ // catalog DB cannot answer (gate, off-switch, backfill, rebuild, any error).
80
+ // In the daemon, the catalog DB worker runs the same engine off this thread
81
+ // and an ordinary listing is one request to it (catalog-db/index.mjs).
82
+ const ordinaryCatalog = createOrdinaryCatalog();
83
+ const catalogListeners = new Set();
76
84
 
77
- function observeStamp(path) {
78
- try {
79
- return statSessionStamp(path);
80
- } catch {
81
- return null;
82
- }
83
- }
84
-
85
- /** `{ state, value }`: `derive(text)` for the record at `path`, or its cached
86
- * result while the file is provably unchanged. */
87
- function stampedDerivation(kind, path, derive) {
88
- const key = `${kind}|${path}`;
89
- const before = observeStamp(path);
90
- const cached = stampedDerivations.get(key);
91
- if (before && cached && sameSessionStamp(cached.stamp, before)) {
92
- stampedDerivations.delete(key);
93
- stampedDerivations.set(key, cached);
94
- return { state: PROBE_PRESENT, value: cached.value };
95
- }
96
- stampedDerivations.delete(key);
97
- const read = readTextFile(path);
98
- if (read.state !== PROBE_PRESENT) return { state: read.state, value: null };
99
- const value = derive(read.text);
100
- if (isSettledSessionStamp(before) && sameSessionStamp(before, observeStamp(path))) {
101
- stampedDerivations.set(key, { stamp: before, value });
102
- if (stampedDerivations.size > STAMPED_DERIVATION_LIMIT) {
103
- stampedDerivations.delete(stampedDerivations.keys().next().value);
85
+ function notifyCatalogListeners() {
86
+ for (const listener of catalogListeners) {
87
+ try {
88
+ listener();
89
+ } catch {
90
+ /* a listener's failure is its own */
104
91
  }
105
92
  }
106
- return { state: PROBE_PRESENT, value };
107
93
  }
108
94
 
109
- /** Test seam: forget every stamp-keyed derivation. */
110
- export function clearStoredSessionDerivations() {
111
- stampedDerivations.clear();
112
- }
113
-
114
- const SESSION_SUMMARY_INDEX_VERSION = 2;
115
-
116
- // Mirror of lifecycle-api.mjs listLeadSessions visibility: the cold catalog
117
- // must never surface worker/agent dispatches (memory ingest chunks, judges,
118
- // spawned agents) — the authoritative engine excludes them, so a click on
119
- // such a row dead-ends in "Session is not available." (user report).
120
- const LEAD_OWNERS = new Set(['cli', 'user', 'mixdog']);
95
+ ordinaryCatalog.subscribe(notifyCatalogListeners);
96
+ onSessionCatalogDbChange(notifyCatalogListeners);
121
97
 
122
- function isLeadVisibleRow(row) {
123
- const owner = String(row.owner || 'user')
124
- .trim()
125
- .toLowerCase();
126
- if (isAgentOnlySession(row)) return false;
127
- if (!isRootLeadSession(row) && owner && !LEAD_OWNERS.has(owner)) return false;
128
- // Mirror listLeadSessions: a previewless zero-message row is an unusable
129
- // scratch (desktop boot leftovers, crashed first turns) — resuming it
130
- // shows an empty conversation, so the catalog hides it.
131
- if (!row.preview && row.messageCount === 0) return false;
132
- const sourceType = String(row.sourceType || '')
133
- .trim()
134
- .toLowerCase();
135
- const sourceName = String(row.sourceName || '')
136
- .trim()
137
- .toLowerCase();
138
- const agent = String(row.agent || '')
139
- .trim()
140
- .toLowerCase();
141
- return (
142
- agent === 'lead' ||
143
- sourceType === 'lead' ||
144
- sourceType === 'cli' ||
145
- sourceType === 'schedule' ||
146
- sourceType === 'webhook' ||
147
- (!sourceType && !sourceName && owner !== 'agent')
148
- );
98
+ /** `listener()` runs when a background pass finds a session file that changed
99
+ * without a directory or index change, i.e. when an ordinary listing would
100
+ * now differ from the last one. Returns the unsubscribe function. */
101
+ export function subscribeStoredSessionCatalogChanges(listener) {
102
+ catalogListeners.add(listener);
103
+ return () => catalogListeners.delete(listener);
149
104
  }
150
105
 
151
- function cleanText(value, maximum = 240) {
152
- return (
153
- String(value || '')
154
- .replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, ' ')
155
- .replace(/<mcp-instructions>[\s\S]*?<\/mcp-instructions>/gi, ' ')
156
- // Session-context envelope (mirror of session-text.mjs
157
- // stripSessionDisplayEnvelope): the "# Session / Cwd / Model /
158
- // Workflow" header must never become a Recent title.
159
- .replace(/^\s*# Session\r?\n(?:(?:Cwd|Model|Workflow):[^\r\n]*(?:\r?\n|$))+(?:\r?\n)?/i, ' ')
160
- .replace(/^\s*#\s*Session\s+Cwd:\s+\S+(?:\s+Model:[^\r\n]*?)?(?:\s+Workflow:\s+\S+)?\s*/i, ' ')
161
- .replace(/\s+/g, ' ')
162
- .trim()
163
- .slice(0, maximum)
164
- );
106
+ /** Run a background verification of the ordinary catalog now (or join the
107
+ * running one) — here and in the catalog DB worker; resolves when done. */
108
+ export async function verifyStoredSessionCatalog() {
109
+ await Promise.all([ordinaryCatalog.verify(), currentSessionCatalogDb(dataDir())?.verifyFiles()]);
165
110
  }
166
111
 
167
- function messageText(content) {
168
- if (typeof content === 'string') return content;
169
- if (!Array.isArray(content)) return '';
170
- return content
171
- .map((part) => (typeof part === 'string' ? part : String(part?.text || part?.content || '')))
172
- .filter(Boolean)
173
- .join(' ');
174
- }
175
-
176
- function normalizedRow(row, heartbeatAt = 0) {
177
- if (!row || typeof row.id !== 'string' || !isStoredSessionId(row.id)) return null;
178
- return {
179
- id: row.id,
180
- updatedAt: positiveNumber(row.updatedAt, 0),
181
- // Conversation-activity timestamp (mirror of listLeadSessions):
182
- // detach/resume bookkeeping bumps updatedAt in bulk on restarts, so
183
- // Recent must order by lastUsedAt or every restart reshuffles rows.
184
- lastUsedAt: positiveNumber(row.lastUsedAt, 0),
185
- createdAt: positiveNumber(row.createdAt, 0),
186
- lastHeartbeatAt: positiveNumber(row.lastHeartbeatAt, 0),
187
- // Liveness comes from the .hb sidecar mtime alone: stored row fields
188
- // (summary index / final session save) survive completion and must not
189
- // keep the desktop working indicator on after the sidecar is deleted.
190
- heartbeatAt: positiveNumber(heartbeatAt, 0),
191
- closed: row.closed === true,
192
- status: String(row.status || (row.closed === true ? 'closed' : 'idle')),
193
- owner: row.owner || 'user',
194
- agent: row.agent || null,
195
- sourceType: row.sourceType || null,
196
- sourceName: row.sourceName || null,
197
- scopeKey: row.scopeKey || null,
198
- ownerSessionId: row.ownerSessionId || row.parentSessionId || null,
199
- visibility: sessionVisibility(row),
200
- clientHostPid: positiveNumber(row.clientHostPid, 0) || null,
201
- cwd: row.cwd || '',
202
- desktopSession: desktopSession(row.desktopSession, row.cwd),
203
- provider: row.provider || null,
204
- model: row.model || null,
205
- agentTag: row.agentTag || null,
206
- task_id: row.task_id || row.taskId || null,
207
- permission: row.permission || null,
208
- toolPermission: row.toolPermission || null,
209
- messageCount: Math.max(0, Math.floor(Number(row.messageCount) || 0)),
210
- title: cleanText(row.title, 100),
211
- preview: cleanText(row.preview),
212
- generation: typeof row.generation === 'number' ? row.generation : 0,
213
- storageMtimeMs: positiveNumber(row.storageMtimeMs, 0),
214
- storageSize: positiveNumber(row.storageSize, 0),
215
- detachedReason: row.detachedReason || null,
216
- };
112
+ /** Test seam: forget the ordinary catalog and the parsed summary index. */
113
+ export function clearStoredSessionCatalog() {
114
+ ordinaryCatalog.clear();
217
115
  }
218
116
 
219
- function leadRowsWithAgentHeartbeat(rows) {
220
- const agentHeartbeatByOwner = new Map();
221
- for (const row of rows) {
222
- const heartbeatAt = positiveNumber(row?.heartbeatAt, 0);
223
- const ownerSessionId = String(row?.ownerSessionId || '').trim();
224
- const owner = String(row?.owner || '')
225
- .trim()
226
- .toLowerCase();
227
- const agent = String(row?.agent || '')
228
- .trim()
229
- .toLowerCase();
230
- if (!heartbeatAt || !ownerSessionId || (owner !== 'agent' && (!agent || agent === 'lead'))) continue;
231
- agentHeartbeatByOwner.set(ownerSessionId, Math.max(agentHeartbeatByOwner.get(ownerSessionId) || 0, heartbeatAt));
117
+ export function listStoredSessionSummaries(options = {}) {
118
+ // The index is consulted on EVERY path: its rows are the authority that
119
+ // must be retained whenever storage cannot be enumerated or an individual
120
+ // session file cannot be read.
121
+ const index = ordinaryCatalog.loadIndex();
122
+ // An index that EXISTS but is unreadable (EACCES/EIO) is neither missing
123
+ // nor empty: the scan still runs, and when IT cannot enumerate either, the
124
+ // catalog reports nothing rather than inventing an empty truth.
125
+ if (options.refreshFromStorage === true) {
126
+ const heartbeatMtimes = sessionHeartbeatMtimes();
127
+ return scanSessionFiles(heartbeatMtimes, index.rowsById, true) ?? indexListing(index, heartbeatMtimes) ?? [];
232
128
  }
233
- return rows
234
- .filter((row) => row && isLeadVisibleRow(row))
235
- .map((row) => {
236
- const agentHeartbeatAt = agentHeartbeatByOwner.get(row.id) || 0;
237
- return agentHeartbeatAt > 0 ? { ...row, agentHeartbeatAt } : row;
238
- });
129
+ if (options.rebuildIfMissing === false) return indexListing(index, sessionHeartbeatMtimes()) ?? [];
130
+ // A missing/malformed index leaves every file to be parsed (once per
131
+ // stamp); a present one lets unchanged files reuse their durable summary.
132
+ return ordinaryCatalog.refresh(index).rows ?? indexListing(index, sessionHeartbeatMtimes()) ?? [];
239
133
  }
240
134
 
241
- function rowFromSession(session, heartbeatAt = 0) {
242
- const messages = Array.isArray(session?.messages) ? session.messages : [];
243
- const preview =
244
- messages
245
- .filter((message) => message?.role === 'user')
246
- // Cold-path mirror of isSessionPreviewNoise's synthetic skips: compact
247
- // handoffs and runtime notices must not become session titles.
248
- .filter(
249
- (message) =>
250
- !/^\s*(?:a previous model worked on this task|re-attached after compaction\b|reference files:\s|\[mixdog-runtime\]|the async (?:agent|shell) task\b)/i.test(
251
- messageText(message.content)
252
- )
253
- )
254
- .map((message) => cleanText(messageText(message.content)))
255
- .find(Boolean) || '';
256
- return normalizedRow(
257
- {
258
- ...session,
259
- messageCount: messages.filter((message) => message?.role === 'user' || message?.role === 'assistant').length,
260
- preview,
261
- },
262
- heartbeatAt
263
- );
135
+ /** The catalog DB's answer to an ordinary listing (`{ rows, more }`), or
136
+ * null when this process has none or it cannot answer. No filesystem work
137
+ * on this thread: the worker checks storage, applies changes, answers. */
138
+ async function catalogDbListing(page) {
139
+ const db = sessionCatalogDb(dataDir());
140
+ if (!db) return null;
141
+ const answer = await db.list(page);
142
+ // While the DB answers, the JSON path's reconcile state is dead weight.
143
+ if (answer) ordinaryCatalog.clear();
144
+ return answer;
264
145
  }
265
146
 
266
147
  /**
267
- * Read-only scan of the authoritative session files.
268
- * Returns `null` when the directory itself is NOT enumerable (unreadable
269
- * stat / readdir): that is not "no sessions", and the caller must keep the
270
- * authority it already has (the index rows) instead of publishing an empty
271
- * catalog. `indexRowsById` carries those rows so unchanged files can reuse the
272
- * durable summary and a single UNREADABLE file retains its last known row
273
- * instead of vanishing.
148
+ * listStoredSessionSummaries for async callers (the daemon's session views).
149
+ * In the daemon the ordinary listing comes from the session catalog DB
150
+ * worker; anywhere else, and whenever the DB cannot answer, it is the JSON
151
+ * path. `refreshFromStorage` and `rebuildIfMissing: false` always take the
152
+ * JSON/file path.
274
153
  */
275
- function scanSessionFiles(
276
- heartbeatMtimes = sessionHeartbeatMtimes(),
277
- indexRowsById = new Map(),
278
- { forceRead = false } = {}
279
- ) {
280
- const directory = join(dataDir(), 'sessions');
281
- const dirProbe = probePath(directory);
282
- if (dirProbe.state === PROBE_ABSENT) return [];
283
- if (dirProbe.state !== PROBE_PRESENT) return null;
284
- let entries;
285
- try {
286
- entries = readdirSync(directory);
287
- } catch {
288
- return null;
289
- }
290
- const rows = [];
291
- for (const filename of entries) {
292
- if (!filename.endsWith('.json')) continue;
293
- const storageId = filename.slice(0, -5);
294
- const path = join(directory, filename);
295
- const retained = indexRowsById.get(storageId);
296
- const probe = probePath(path);
297
- // Provably gone: the row legitimately disappears with the file.
298
- if (probe.state === PROBE_ABSENT) continue;
299
- if (probe.state !== PROBE_PRESENT) {
300
- if (retained) rows.push(retained);
301
- continue;
302
- }
303
- if (!forceRead && retained) {
304
- const exactFingerprint =
305
- retained.storageMtimeMs > 0 &&
306
- retained.storageSize > 0 &&
307
- retained.storageMtimeMs === probe.mtimeMs &&
308
- retained.storageSize === probe.size;
309
- if (exactFingerprint) {
310
- rows.push(retained);
311
- continue;
312
- }
313
- }
314
- // A file the index does not describe (never saved by the store, or
315
- // saved after the index was written) is parsed once per full stamp.
316
- const read = stampedDerivation('row', path, (text) => {
317
- // Same strict authority as the store: a duplicate/ambiguous top-level
318
- // record or a foreign identity is not this file's session.
319
- const record = readTopLevelLifecycleRecord(text);
320
- if (isLifecycleUnreadable(record) || record.id !== storageId) return null;
321
- return rowFromSession(record.doc, 0);
322
- });
323
- // Provably gone: the row legitimately disappears with the file.
324
- if (read.state === PROBE_ABSENT) continue;
325
- if (read.state !== PROBE_PRESENT) {
326
- // Exists and could not be read (EACCES/EIO): retain the last known
327
- // row rather than let a transient IO error delete it from view.
328
- if (retained) rows.push(retained);
329
- continue;
330
- }
331
- if (read.value) {
332
- rows.push({
333
- ...structuredClone(read.value),
334
- heartbeatAt: positiveNumber(heartbeatMtimes.get(storageId), 0),
335
- storageMtimeMs: probe.mtimeMs,
336
- storageSize: probe.size,
337
- });
338
- }
154
+ export async function listStoredSessionSummariesAsync(options = {}) {
155
+ if (options.refreshFromStorage === true || options.rebuildIfMissing === false) {
156
+ return listStoredSessionSummaries(options);
339
157
  }
340
- return leadRowsWithAgentHeartbeat(rows).sort(byRecentActivity);
158
+ const answer = await catalogDbListing(null);
159
+ return answer ? answer.rows : listStoredSessionSummaries();
341
160
  }
342
161
 
343
- // Newest conversation activity first (see normalizedRow's lastUsedAt note).
344
- function byRecentActivity(left, right) {
345
- return (right.lastUsedAt || right.updatedAt || 0) - (left.lastUsedAt || left.updatedAt || 0);
346
- }
162
+ const DEFAULT_PAGE_LIMIT = 50;
163
+ const MAX_PAGE_LIMIT = 1000;
347
164
 
348
- export function listStoredSessionSummaries(options = {}) {
349
- const heartbeatMtimes = sessionHeartbeatMtimes();
350
- const indexPath = join(dataDir(), 'session-summaries.json');
351
- // The index is read on EVERY path (it is one small file): its rows are the
352
- // authority that must be retained whenever storage cannot be enumerated or
353
- // an individual session file cannot be read.
354
- const indexRowsById = new Map();
355
- let indexRows = null;
356
- // The delta log (per-session updates since the last compaction) is read
357
- // before the base; see store-summary-log.mjs.
358
- const logText = readSummaryLogText(indexPath);
359
- const indexRead = readTextFile(indexPath);
360
- if (indexRead.state === PROBE_PRESENT) {
361
- try {
362
- const index = applySummaryLogText(JSON.parse(indexRead.text), logText);
363
- if (Number(index?.version) === SESSION_SUMMARY_INDEX_VERSION) {
364
- const normalizedRows = (Array.isArray(index.rows) ? index.rows : [])
365
- .map((row) => normalizedRow(row, heartbeatMtimes.get(row?.id) || 0))
366
- .filter(Boolean);
367
- for (const row of normalizedRows) indexRowsById.set(row.id, row);
368
- indexRows = leadRowsWithAgentHeartbeat(normalizedRows).sort(byRecentActivity);
369
- }
370
- } catch {
371
- /* malformed sidecar: the files below are the authority */
372
- }
373
- }
374
- // An index that EXISTS but is unreadable (EACCES/EIO) is neither missing
375
- // nor empty: the scan below still runs, and when IT cannot enumerate
376
- // either, the catalog reports nothing rather than inventing an empty truth.
377
- const scan = (forceRead = false) => scanSessionFiles(heartbeatMtimes, indexRowsById, { forceRead });
378
- if (options.refreshFromStorage === true) return scan(true) ?? indexRows ?? [];
379
- if (indexRows) {
380
- if (options.rebuildIfMissing === false) return indexRows;
381
- // Enumerate identities to detect deletions, stat known files, and parse
382
- // only rows newer than the index. Cold-start cost therefore scales with
383
- // changed transcripts rather than total transcript bytes.
384
- return scan(false) ?? indexRows;
165
+ /**
166
+ * One page of the ordinary listing: `{ sessions, nextCursor }` in catalog
167
+ * order (newest activity first, ties by id), `limit` rows (default 50, at
168
+ * most 1000) after the keyset `cursor` (an earlier page's `nextCursor`;
169
+ * absent = first page). Answered like listStoredSessionSummariesAsync; from
170
+ * the DB only the requested rows are transferred.
171
+ */
172
+ export async function listStoredSessionSummaryPage(options = {}) {
173
+ const requested = Math.floor(Number(options.limit));
174
+ const limit = requested > 0 ? Math.min(requested, MAX_PAGE_LIMIT) : DEFAULT_PAGE_LIMIT;
175
+ const after = decodeCatalogCursor(options.cursor);
176
+ const answer = await catalogDbListing({ limit, after });
177
+ if (answer) {
178
+ return {
179
+ sessions: answer.rows,
180
+ nextCursor: answer.more && answer.rows.length ? encodeCatalogCursor(answer.rows.at(-1)) : null,
181
+ };
385
182
  }
386
- // Missing/malformed index is the sole cold path that must rebuild every
387
- // summary from canonical session bytes.
388
- return options.rebuildIfMissing === false ? [] : (scan(true) ?? indexRows ?? []);
183
+ // The JSON listing is already in catalog order.
184
+ const ordered = listStoredSessionSummaries().filter((row) => isAfterCursor(row, after));
185
+ const sessions = ordered.slice(0, limit);
186
+ return {
187
+ sessions,
188
+ nextCursor: ordered.length > limit ? encodeCatalogCursor(sessions.at(-1)) : null,
189
+ };
389
190
  }
390
191
 
391
192
  /** Exact, fail-closed existence check for a durable session address.
@@ -426,11 +227,13 @@ function storedSessionMetadata(sessionId, session) {
426
227
  visibility: sessionVisibility(session),
427
228
  agentTag: session.agentTag || null,
428
229
  cwd: session.cwd || '',
230
+ ...(workspaceSummary(session.workspace) ? { workspace: workspaceSummary(session.workspace) } : {}),
429
231
  provider: session.provider || null,
430
232
  model: session.model || null,
431
233
  presetName: session.presetName || session.profileId || null,
432
234
  effort: session.effort || null,
433
235
  fast: session.fast === true,
236
+ ...(session.autoEffort === false ? { autoEffort: false } : {}),
434
237
  modelParameters: session.modelParameters || null,
435
238
  taskType: session.taskType || null,
436
239
  permission: session.permission || null,
@@ -452,11 +255,12 @@ function storedTranscriptInputPaths(sessionId) {
452
255
  return {
453
256
  recordPath: join(dataDir(), 'sessions', `${sessionId}.json`),
454
257
  checkpointPath: join(dataDir(), 'turn-checkpoints', `${sessionId}.json`),
258
+ // Written after the compaction commit that names it, so its identity
259
+ // joins the projection's.
260
+ archivePath: compactionArchivePath(join(dataDir(), 'sessions'), sessionId),
455
261
  };
456
262
  }
457
263
 
458
- const STAMP_ABSENT_CODES = new Set(['ENOENT', 'ENOTDIR']);
459
-
460
264
  /** Settled identity of the files readStoredSessionTranscript projects from:
461
265
  * the canonical record and its turn-checkpoint sidecar. Null while either is
462
266
  * too fresh to vouch for by stat alone (isSettledSessionStamp), unreadable,
@@ -464,7 +268,7 @@ const STAMP_ABSENT_CODES = new Set(['ENOENT', 'ENOTDIR']);
464
268
  export function storedSessionTranscriptStamp(id) {
465
269
  const sessionId = String(id || '').trim();
466
270
  if (!isStoredSessionId(sessionId)) return null;
467
- const { recordPath, checkpointPath } = storedTranscriptInputPaths(sessionId);
271
+ const { recordPath, checkpointPath, archivePath } = storedTranscriptInputPaths(sessionId);
468
272
  const settled = (path, absent) => {
469
273
  let stamp;
470
274
  try {
@@ -477,7 +281,74 @@ export function storedSessionTranscriptStamp(id) {
477
281
  };
478
282
  const record = settled(recordPath, null);
479
283
  const checkpoint = record && settled(checkpointPath, 'absent');
480
- return checkpoint ? `${record}|${checkpoint}` : null;
284
+ const archive = checkpoint && settled(archivePath, 'absent');
285
+ return archive ? `${record}|${checkpoint}|${archive}` : null;
286
+ }
287
+
288
+ /** Projects the strictly parsed record `text` (read at full stamp `stamp`, or
289
+ * null when not provably stable) into the cached transcript value. */
290
+ async function produceStoredTranscript(text, stamp, context) {
291
+ const { sessionId, recordPath, itemLimit, includeMessages, mode, offloadable, checkpointAbsent, timing } = context;
292
+ // A large record with no checkpoint sidecar parses and projects on a
293
+ // worker thread: the same pipeline, off the daemon event loop. It hands
294
+ // no parse to the load cache (a later resume reads the file itself).
295
+ if (offloadable && text.length >= OFFLOAD_MIN_CHARS) {
296
+ try {
297
+ timing.worker = storedTranscriptWorkerRunning() ? 'warm' : 'cold';
298
+ const workerStartedAt = performance.now();
299
+ const answer = await projectStoredTranscriptOffThread({ sessionId, text, itemLimit });
300
+ timing.workerMs = performance.now() - workerStartedAt;
301
+ Object.assign(timing, answer.timing);
302
+ if (answer.unreadable) return null;
303
+ if (stamp) readCanonicalLifecycle.rememberStrictVerdict(recordPath, stamp, answer.lifecycle);
304
+ // Stamps are process-unique per module realm; re-issue in this one.
305
+ answer.value.projectionStamp = nextProjectionStamp();
306
+ return answer.value;
307
+ } catch {
308
+ // Worker unavailable: fall through to the in-process pipeline.
309
+ }
310
+ }
311
+ const parseStartedAt = performance.now();
312
+ const record = readTopLevelLifecycleRecord(text);
313
+ timing.parseMs = performance.now() - parseStartedAt;
314
+ if (isLifecycleUnreadable(record) || record.id !== sessionId) return null;
315
+ // The same strict verdict the lifecycle authority would reach for these
316
+ // bytes (storedSessionExists runs next when the pane's runtime loads).
317
+ if (stamp) readCanonicalLifecycle.rememberStrictVerdict(recordPath, stamp, record);
318
+ // Hand the parse to the session load cache BEFORE projecting: a resume
319
+ // that runs while this projection is still being built (the daemon
320
+ // resumes the panes it restores) claims it instead of reading the file
321
+ // again. The projection path loads the store anyway, so loading its
322
+ // cache first costs nothing and gives the hand-off somewhere to go on
323
+ // the process's first read.
324
+ let handedOver = false;
325
+ if (stamp) {
326
+ await import('./store/load-cache.mjs');
327
+ handedOver = offerStrictSessionRecord(recordPath, stamp, record, text.length);
328
+ }
329
+ // Once handed over, the record is a runtime's mutable session. The
330
+ // projection is then built from a private parse of the same bytes, so
331
+ // nothing it retains aliases that session and it needs no copying. With
332
+ // a turn checkpoint the projection recovers through loadSession, the very
333
+ // load the hand-off serves, and projects the load cache's session
334
+ // exactly as before; only that projection is detached.
335
+ const projectStartedAt = performance.now();
336
+ const value = await projectStoredTranscript(
337
+ sessionId,
338
+ handedOver && checkpointAbsent ? JSON.parse(text) : record.doc,
339
+ {
340
+ itemLimit,
341
+ includeMessages,
342
+ checkpointAbsent,
343
+ }
344
+ );
345
+ timing.projectMs = performance.now() - projectStartedAt;
346
+ if (!handedOver || checkpointAbsent || !value || mode !== 'items') return value;
347
+ try {
348
+ return structuredClone(value);
349
+ } catch {
350
+ return value;
351
+ }
481
352
  }
482
353
 
483
354
  /** Read exactly one persisted session for a visible desktop pane. This never
@@ -489,8 +360,13 @@ export async function readStoredSessionTranscript(id, options = {}) {
489
360
  if (!isStoredSessionId(sessionId)) return null;
490
361
  // Same strict authority as the store, and the same fail-closed rule: an
491
362
  // absent, unreadable, ambiguous or foreign record yields no transcript.
492
- const { recordPath, checkpointPath } = storedTranscriptInputPaths(sessionId);
363
+ const { recordPath, checkpointPath, archivePath } = storedTranscriptInputPaths(sessionId);
493
364
  if (options.metadataOnly === true) return readStoredSessionMetadata(recordPath, sessionId);
365
+ // One page of the display-only compaction archive (the live head loader
366
+ // pages it once a runtime's in-memory rows are exhausted).
367
+ if (options.compactionArchive && typeof options.compactionArchive === 'object') {
368
+ return readCompactionArchivePage(join(dataDir(), 'sessions'), sessionId, options.compactionArchive);
369
+ }
494
370
  // The checkpoint sidecar shapes the projection as much as the record does,
495
371
  // so its identity joins the cache fingerprint. Only a PROVABLY absent
496
372
  // checkpoint skips recovery: an unreadable probe must not silently
@@ -502,6 +378,7 @@ export async function readStoredSessionTranscript(id, options = {}) {
502
378
  if (recordStat.state === PROBE_ABSENT) return readArchivedAgentResult(sessionId);
503
379
  if (recordStat.state !== PROBE_PRESENT) return null;
504
380
  const checkpoint = probePath(checkpointPath);
381
+ const archive = probePath(archivePath);
505
382
  const requestedLimit = Number(options.transcriptItemLimit);
506
383
  const itemLimit = Number.isFinite(requestedLimit) && requestedLimit > 0 ? requestedLimit : Number.POSITIVE_INFINITY;
507
384
  let readState = PROBE_PRESENT;
@@ -510,10 +387,21 @@ export async function readStoredSessionTranscript(id, options = {}) {
510
387
  let readStamp = null;
511
388
  // The strict record parse (full JSON + duplicate-key scan) is itself a
512
389
  // large share of a cold read, so it only runs when the content is new.
513
- const mode = options.includeMessages === true ? 'messages' : 'items';
390
+ const includeMessages = options.includeMessages === true;
391
+ const mode = includeMessages ? 'messages' : 'items';
392
+ const checkpointAbsent = checkpoint.state === PROBE_ABSENT;
514
393
  // Large, checkpoint-free item projections run off the daemon event loop.
515
- const offloadable =
516
- options.includeMessages !== true && checkpoint.state === PROBE_ABSENT && recordStat.size >= OFFLOAD_MIN_CHARS;
394
+ const offloadable = !includeMessages && checkpointAbsent && recordStat.size >= OFFLOAD_MIN_CHARS;
395
+ const produceContext = {
396
+ sessionId,
397
+ recordPath,
398
+ itemLimit,
399
+ includeMessages,
400
+ mode,
401
+ offloadable,
402
+ checkpointAbsent,
403
+ timing,
404
+ };
517
405
  const { value, hit, read } = await storedTranscriptCache.read({
518
406
  key: `${sessionId}|${itemLimit}|${mode}`,
519
407
  // Growing history windows of one session replace each other.
@@ -525,6 +413,9 @@ export async function readStoredSessionTranscript(id, options = {}) {
525
413
  checkpoint.size,
526
414
  checkpoint.ino,
527
415
  checkpoint.dev,
416
+ archive.state,
417
+ archive.mtimeMs,
418
+ archive.size,
528
419
  ].join(':'),
529
420
  fileStat: recordStat,
530
421
  loadText: async () => {
@@ -547,70 +438,7 @@ export async function readStoredSessionTranscript(id, options = {}) {
547
438
  readStamp = before && sameSessionStamp(before, observeStamp(recordPath)) ? before : null;
548
439
  return body.text;
549
440
  },
550
- produce: async (text) => {
551
- const stamp = readStamp;
552
- // A large record with no checkpoint sidecar parses and projects on a
553
- // worker thread: the same pipeline, off the daemon event loop. It hands
554
- // no parse to the load cache (a later resume reads the file itself).
555
- if (offloadable && text.length >= OFFLOAD_MIN_CHARS) {
556
- try {
557
- timing.worker = storedTranscriptWorkerRunning() ? 'warm' : 'cold';
558
- const workerStartedAt = performance.now();
559
- const answer = await projectStoredTranscriptOffThread({ sessionId, text, itemLimit });
560
- timing.workerMs = performance.now() - workerStartedAt;
561
- Object.assign(timing, answer.timing);
562
- if (answer.unreadable) return null;
563
- if (stamp) readCanonicalLifecycle.rememberStrictVerdict(recordPath, stamp, answer.lifecycle);
564
- // Stamps are process-unique per module realm; re-issue in this one.
565
- answer.value.projectionStamp = nextProjectionStamp();
566
- return answer.value;
567
- } catch {
568
- // Worker unavailable: fall through to the in-process pipeline.
569
- }
570
- }
571
- const parseStartedAt = performance.now();
572
- const record = readTopLevelLifecycleRecord(text);
573
- timing.parseMs = performance.now() - parseStartedAt;
574
- if (isLifecycleUnreadable(record) || record.id !== sessionId) return null;
575
- // The same strict verdict the lifecycle authority would reach for these
576
- // bytes (storedSessionExists runs next when the pane's runtime loads).
577
- if (stamp) readCanonicalLifecycle.rememberStrictVerdict(recordPath, stamp, record);
578
- const checkpointAbsent = checkpoint.state === PROBE_ABSENT;
579
- // Hand the parse to the session load cache BEFORE projecting: a resume
580
- // that runs while this projection is still being built (the daemon
581
- // resumes the panes it restores) claims it instead of reading the file
582
- // again. The projection path loads the store anyway, so loading its
583
- // cache first costs nothing and gives the hand-off somewhere to go on
584
- // the process's first read.
585
- let handedOver = false;
586
- if (stamp) {
587
- await import('./store/load-cache.mjs');
588
- handedOver = offerStrictSessionRecord(recordPath, stamp, record, text.length);
589
- }
590
- // Once handed over, the record is a runtime's mutable session. The
591
- // projection is then built from a private parse of the same bytes, so
592
- // nothing it retains aliases that session and it needs no copying. With
593
- // a turn checkpoint the projection recovers through loadSession, the very
594
- // load the hand-off serves, and projects the load cache's session
595
- // exactly as before; only that projection is detached.
596
- const projectStartedAt = performance.now();
597
- const value = await projectStoredTranscript(
598
- sessionId,
599
- handedOver && checkpointAbsent ? JSON.parse(text) : record.doc,
600
- {
601
- itemLimit,
602
- includeMessages: options.includeMessages === true,
603
- checkpointAbsent,
604
- }
605
- );
606
- timing.projectMs = performance.now() - projectStartedAt;
607
- if (!handedOver || checkpointAbsent || !value || mode !== 'items') return value;
608
- try {
609
- return structuredClone(value);
610
- } catch {
611
- return value;
612
- }
613
- },
441
+ produce: (text) => produceStoredTranscript(text, readStamp, produceContext),
614
442
  });
615
443
  // The record vanished between stat and read: same answer as an absent probe.
616
444
  if (readState === PROBE_ABSENT) return readArchivedAgentResult(sessionId);