@vellumai/assistant 0.8.4 → 0.8.5

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 (438) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docs/browser-use-architecture-phase2.md +1 -1
  3. package/knip.json +2 -1
  4. package/openapi.yaml +809 -11
  5. package/package.json +1 -1
  6. package/src/__tests__/anthropic-provider.test.ts +34 -37
  7. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
  8. package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
  9. package/src/__tests__/audit-log-rotation.test.ts +70 -16
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
  11. package/src/__tests__/btw-routes.test.ts +2 -3
  12. package/src/__tests__/call-controller.test.ts +0 -1
  13. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  14. package/src/__tests__/channel-guardian.test.ts +3 -3
  15. package/src/__tests__/checker.test.ts +6 -15
  16. package/src/__tests__/compaction-events.test.ts +1 -0
  17. package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
  18. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
  19. package/src/__tests__/computer-use-tools.test.ts +2 -4
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  21. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
  22. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
  24. package/src/__tests__/conversation-agent-loop.test.ts +163 -122
  25. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
  26. package/src/__tests__/conversation-clear-safety.test.ts +25 -25
  27. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  29. package/src/__tests__/conversation-error.test.ts +31 -0
  30. package/src/__tests__/conversation-fork-crud.test.ts +178 -15
  31. package/src/__tests__/conversation-lifecycle.test.ts +52 -11
  32. package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
  33. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  34. package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
  36. package/src/__tests__/conversation-skill-tools.test.ts +2 -5
  37. package/src/__tests__/conversation-store.test.ts +1 -1
  38. package/src/__tests__/conversation-sync-tags.test.ts +99 -32
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  40. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  42. package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
  43. package/src/__tests__/credential-execution-tools.test.ts +6 -6
  44. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  45. package/src/__tests__/credential-vault-unit.test.ts +2 -2
  46. package/src/__tests__/dynamic-page-surface.test.ts +2 -2
  47. package/src/__tests__/email-html-renderer.test.ts +12 -0
  48. package/src/__tests__/gateway-flag-listener.test.ts +237 -0
  49. package/src/__tests__/gemini-provider.test.ts +78 -0
  50. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  51. package/src/__tests__/guardian-outbound-http.test.ts +7 -5
  52. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  53. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  54. package/src/__tests__/heartbeat-service.test.ts +4 -0
  55. package/src/__tests__/host-shell-tool.test.ts +1 -1
  56. package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
  57. package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
  58. package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
  59. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
  60. package/src/__tests__/llm-resolver.test.ts +77 -9
  61. package/src/__tests__/llm-usage-store.test.ts +66 -0
  62. package/src/__tests__/logger.test.ts +89 -0
  63. package/src/__tests__/mcp-abort-signal.test.ts +2 -2
  64. package/src/__tests__/media-generate-image.test.ts +31 -0
  65. package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
  66. package/src/__tests__/model-intents.test.ts +2 -4
  67. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  68. package/src/__tests__/onboarding-template-contract.test.ts +1 -1
  69. package/src/__tests__/openai-provider.test.ts +46 -0
  70. package/src/__tests__/openai-responses-provider.test.ts +114 -12
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
  72. package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
  73. package/src/__tests__/platform.test.ts +2 -2
  74. package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
  75. package/src/__tests__/plugin-bootstrap.test.ts +2 -2
  76. package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
  77. package/src/__tests__/plugin-types.test.ts +3 -2
  78. package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
  79. package/src/__tests__/pricing.test.ts +12 -0
  80. package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
  81. package/src/__tests__/registry.test.ts +2 -8
  82. package/src/__tests__/require-fresh-approval.test.ts +2 -2
  83. package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
  84. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  85. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  86. package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
  87. package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
  88. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  89. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  90. package/src/__tests__/suggestion-routes.test.ts +1 -0
  91. package/src/__tests__/sync-message-contract.test.ts +59 -0
  92. package/src/__tests__/system-prompt.test.ts +145 -131
  93. package/src/__tests__/terminal-tools.test.ts +1 -1
  94. package/src/__tests__/tool-approval-handler.test.ts +1 -5
  95. package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
  96. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
  98. package/src/__tests__/tool-executor.test.ts +9 -62
  99. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
  100. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  101. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
  102. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  103. package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
  104. package/src/__tests__/usage-routes.test.ts +3 -0
  105. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  106. package/src/__tests__/workspace-git-service.test.ts +6 -5
  107. package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
  108. package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
  109. package/src/acp/prepare-agent-env.ts +78 -0
  110. package/src/acp/session-manager.ts +1 -1
  111. package/src/agent/loop.ts +8 -0
  112. package/src/api/README.md +5 -0
  113. package/src/api/index.ts +4 -0
  114. package/src/api/package.json +10 -0
  115. package/src/background-wake/background-wake-routes.test.ts +233 -0
  116. package/src/background-wake/runtime-registry.ts +24 -0
  117. package/src/cli/commands/__tests__/browser.test.ts +23 -5
  118. package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
  119. package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
  120. package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
  121. package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
  122. package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
  123. package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
  124. package/src/cli/commands/browser.ts +247 -0
  125. package/src/cli/commands/domain.ts +91 -41
  126. package/src/cli/commands/inference.ts +93 -40
  127. package/src/cli/commands/memory-v2-compare-render.ts +115 -0
  128. package/src/cli/commands/memory-v2.ts +176 -1
  129. package/src/cli/commands/memory-v3-render.ts +344 -0
  130. package/src/cli/commands/memory-v3.ts +316 -0
  131. package/src/cli/program.ts +2 -0
  132. package/src/config/assistant-feature-flags.ts +21 -9
  133. package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
  134. package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
  135. package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
  136. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  137. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  138. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
  139. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
  140. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
  141. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
  142. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
  143. package/src/config/bundled-tool-registry.ts +2 -0
  144. package/src/config/call-site-defaults.ts +7 -6
  145. package/src/config/feature-flag-registry.json +16 -0
  146. package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
  147. package/src/config/schemas/call-site-catalog.ts +21 -7
  148. package/src/config/schemas/llm.ts +12 -1
  149. package/src/config/schemas/memory-v2.ts +246 -0
  150. package/src/config/schemas/memory.ts +2 -1
  151. package/src/context/compactor.ts +52 -0
  152. package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
  153. package/src/conversations/message-consolidation.ts +404 -0
  154. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
  155. package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
  156. package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
  157. package/src/daemon/conversation-agent-loop.ts +126 -76
  158. package/src/daemon/conversation-error.ts +31 -1
  159. package/src/daemon/conversation-lifecycle.ts +27 -22
  160. package/src/daemon/conversation-runtime-assembly.ts +10 -9
  161. package/src/daemon/conversation-tool-setup.ts +63 -3
  162. package/src/daemon/conversation-usage.ts +2 -0
  163. package/src/daemon/conversation.ts +14 -29
  164. package/src/daemon/disk-pressure-guard.ts +14 -2
  165. package/src/daemon/handlers/config-model.test.ts +1 -0
  166. package/src/daemon/handlers/conversations.ts +11 -3
  167. package/src/daemon/host-browser-proxy.ts +5 -5
  168. package/src/daemon/host-cu-proxy.ts +4 -4
  169. package/src/daemon/host-file-proxy.ts +4 -4
  170. package/src/daemon/host-proxy-base.ts +4 -4
  171. package/src/daemon/host-transfer-proxy.ts +10 -10
  172. package/src/daemon/lifecycle.ts +23 -20
  173. package/src/daemon/meet-manifest-loader.ts +1 -7
  174. package/src/daemon/message-types/conversations.ts +6 -9
  175. package/src/daemon/message-types/home.ts +1 -13
  176. package/src/daemon/message-types/messages.ts +6 -14
  177. package/src/daemon/message-types/sync.ts +14 -0
  178. package/src/daemon/shutdown-handlers.ts +24 -5
  179. package/src/daemon/switch-inference-profile-tool.ts +52 -0
  180. package/src/daemon/tool-setup-types.ts +13 -0
  181. package/src/events/relationship-state-updated.ts +25 -0
  182. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
  183. package/src/home/home-greeting.ts +0 -9
  184. package/src/home/suggested-prompts.ts +0 -9
  185. package/src/ipc/gateway-flag-listener.ts +123 -0
  186. package/src/ipc/skill-routes/registries.ts +8 -12
  187. package/src/memory/__tests__/db-async-query.test.ts +165 -0
  188. package/src/memory/__tests__/db-maintenance.test.ts +115 -0
  189. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
  190. package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
  191. package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
  192. package/src/memory/auto-analysis-enqueue.ts +5 -1
  193. package/src/memory/conversation-crud.ts +71 -70
  194. package/src/memory/conversation-starters-cadence.ts +3 -1
  195. package/src/memory/conversation-title-service.ts +19 -3
  196. package/src/memory/db-async-query.ts +214 -0
  197. package/src/memory/db-init.ts +10 -0
  198. package/src/memory/db-maintenance.ts +30 -21
  199. package/src/memory/graph/bootstrap.ts +8 -1
  200. package/src/memory/graph/capability-seed.ts +7 -3
  201. package/src/memory/graph/conversation-graph-memory.ts +100 -17
  202. package/src/memory/graph/extraction.ts +1 -5
  203. package/src/memory/graph/graph-search.ts +7 -1
  204. package/src/memory/indexer.ts +28 -18
  205. package/src/memory/job-handlers/cleanup.ts +76 -18
  206. package/src/memory/job-handlers/conversation-starters.ts +1 -4
  207. package/src/memory/jobs/embed-pkb-file.ts +6 -1
  208. package/src/memory/jobs-store.ts +14 -0
  209. package/src/memory/jobs-worker.ts +55 -22
  210. package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
  211. package/src/memory/llm-request-log-source-local.ts +7 -0
  212. package/src/memory/llm-request-log-source.ts +9 -2
  213. package/src/memory/llm-request-log-store.ts +43 -1
  214. package/src/memory/llm-usage-store.ts +24 -0
  215. package/src/memory/memory-retrospective-enqueue.ts +8 -1
  216. package/src/memory/memory-retrospective-job.ts +5 -0
  217. package/src/memory/memory-v2-activation-log-store.ts +15 -6
  218. package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
  219. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
  220. package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
  221. package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
  222. package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
  223. package/src/memory/migrations/index.ts +17 -0
  224. package/src/memory/migrations/registry.ts +33 -0
  225. package/src/memory/schema/conversations.ts +1 -1
  226. package/src/memory/schema/infrastructure.ts +21 -0
  227. package/src/memory/tool-usage-store.ts +36 -8
  228. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
  229. package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
  230. package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
  231. package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  232. package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
  233. package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
  234. package/src/memory/v2/__tests__/injection.test.ts +127 -98
  235. package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
  236. package/src/memory/v2/__tests__/router.test.ts +171 -3
  237. package/src/memory/v2/harness/compare.ts +57 -0
  238. package/src/memory/v2/harness/metrics.ts +124 -0
  239. package/src/memory/v2/harness/oracle.ts +145 -0
  240. package/src/memory/v2/harness/replay-input.ts +224 -0
  241. package/src/memory/v2/harness/retriever.ts +74 -0
  242. package/src/memory/v2/harness/router-retriever.ts +43 -0
  243. package/src/memory/v2/harness/runner.ts +106 -0
  244. package/src/memory/v2/harness/trace.ts +58 -0
  245. package/src/memory/v2/injection.ts +21 -15
  246. package/src/memory/v2/prompts/router.ts +26 -1
  247. package/src/memory/v2/qdrant.ts +14 -2
  248. package/src/memory/v2/router.ts +171 -18
  249. package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
  250. package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
  251. package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
  252. package/src/memory/v3/__tests__/edges.test.ts +563 -0
  253. package/src/memory/v3/__tests__/filter.test.ts +512 -0
  254. package/src/memory/v3/__tests__/gate.test.ts +574 -0
  255. package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
  256. package/src/memory/v3/__tests__/loop.test.ts +530 -0
  257. package/src/memory/v3/__tests__/retriever.test.ts +226 -0
  258. package/src/memory/v3/__tests__/scouts.test.ts +440 -0
  259. package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
  260. package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
  261. package/src/memory/v3/__tests__/traversal.test.ts +469 -0
  262. package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
  263. package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
  264. package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
  265. package/src/memory/v3/__tests__/validate.test.ts +245 -0
  266. package/src/memory/v3/auto-edges.ts +223 -0
  267. package/src/memory/v3/coactivation-store.ts +124 -0
  268. package/src/memory/v3/consolidation-job.ts +323 -0
  269. package/src/memory/v3/edge-learning-job.ts +160 -0
  270. package/src/memory/v3/edges.ts +249 -0
  271. package/src/memory/v3/filter.ts +281 -0
  272. package/src/memory/v3/gate.ts +334 -0
  273. package/src/memory/v3/index-composition.ts +113 -0
  274. package/src/memory/v3/llm-capture.ts +46 -0
  275. package/src/memory/v3/loop.ts +382 -0
  276. package/src/memory/v3/maintenance.ts +144 -0
  277. package/src/memory/v3/prompt-context.ts +33 -0
  278. package/src/memory/v3/prompts/consolidation.ts +458 -0
  279. package/src/memory/v3/prompts/system-prompts.ts +196 -0
  280. package/src/memory/v3/retriever.ts +33 -0
  281. package/src/memory/v3/scouts.ts +420 -0
  282. package/src/memory/v3/shadow-middleware.ts +305 -0
  283. package/src/memory/v3/traversal.ts +206 -0
  284. package/src/memory/v3/tree-index.ts +237 -0
  285. package/src/memory/v3/tree-store.ts +394 -0
  286. package/src/memory/v3/tree-walk.ts +351 -0
  287. package/src/memory/v3/types.ts +65 -0
  288. package/src/memory/v3/validate.ts +300 -0
  289. package/src/notifications/adapters/macos.ts +18 -1
  290. package/src/notifications/adapters/platform.ts +1 -1
  291. package/src/notifications/decision-engine.ts +1 -4
  292. package/src/notifications/emit-signal.ts +29 -49
  293. package/src/permissions/prompter.ts +3 -3
  294. package/src/permissions/question-prompter.ts +5 -2
  295. package/src/permissions/secret-prompter.ts +2 -2
  296. package/src/plugin-api/index.ts +4 -0
  297. package/src/plugin-api/types.ts +7 -33
  298. package/src/plugins/defaults/index.ts +6 -0
  299. package/src/plugins/defaults/injectors.ts +18 -11
  300. package/src/plugins/external-plugin-loader.ts +5 -68
  301. package/src/plugins/types.ts +11 -16
  302. package/src/proactive-artifact/aux-message-injector.ts +17 -4
  303. package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
  304. package/src/prompts/persona-resolver.ts +36 -21
  305. package/src/prompts/sections.ts +39 -7
  306. package/src/prompts/system-prompt.ts +50 -185
  307. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  308. package/src/prompts/templates/system-sections.ts +230 -8
  309. package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
  310. package/src/providers/__tests__/retry-callsite.test.ts +85 -5
  311. package/src/providers/anthropic/client.ts +32 -66
  312. package/src/providers/call-site-routing.ts +14 -2
  313. package/src/providers/connection-model-compat.ts +38 -0
  314. package/src/providers/connection-resolution.ts +16 -2
  315. package/src/providers/gemini/client.ts +49 -6
  316. package/src/providers/inference/adapter-factory.ts +3 -0
  317. package/src/providers/minimax/client.ts +106 -0
  318. package/src/providers/model-catalog.ts +43 -0
  319. package/src/providers/model-intents.ts +1 -1
  320. package/src/providers/openai/chat-completions-provider.ts +6 -3
  321. package/src/providers/openai/codex-models.ts +18 -0
  322. package/src/providers/openai/responses-provider.ts +78 -21
  323. package/src/providers/provider-send-message.ts +7 -1
  324. package/src/providers/retry.ts +34 -3
  325. package/src/providers/thinking-config.ts +26 -1
  326. package/src/providers/usage-tracking.ts +2 -0
  327. package/src/runtime/AGENTS.md +2 -2
  328. package/src/runtime/agent-wake.ts +1 -0
  329. package/src/runtime/assistant-event-hub.ts +76 -6
  330. package/src/runtime/auth/route-policy.ts +36 -0
  331. package/src/runtime/btw-sidechain.ts +0 -6
  332. package/src/runtime/http-types.ts +0 -2
  333. package/src/runtime/migrations/vbundle-builder.ts +10 -3
  334. package/src/runtime/pending-interactions.ts +0 -1
  335. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
  336. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
  337. package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
  338. package/src/runtime/routes/acp-routes.test.ts +255 -6
  339. package/src/runtime/routes/acp-routes.ts +8 -1
  340. package/src/runtime/routes/avatar-routes.ts +10 -10
  341. package/src/runtime/routes/background-wake-routes.ts +188 -0
  342. package/src/runtime/routes/browser-tabs-routes.ts +200 -0
  343. package/src/runtime/routes/btw-routes.ts +0 -6
  344. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  345. package/src/runtime/routes/conversation-list-routes.ts +12 -4
  346. package/src/runtime/routes/conversation-management-routes.ts +77 -20
  347. package/src/runtime/routes/conversation-query-routes.ts +142 -36
  348. package/src/runtime/routes/conversation-routes.ts +252 -410
  349. package/src/runtime/routes/conversation-starter-routes.ts +6 -3
  350. package/src/runtime/routes/disk-pressure-routes.ts +1 -1
  351. package/src/runtime/routes/domain-routes.ts +60 -10
  352. package/src/runtime/routes/email-routes.ts +5 -2
  353. package/src/runtime/routes/events-routes.ts +54 -10
  354. package/src/runtime/routes/group-routes.ts +24 -8
  355. package/src/runtime/routes/host-browser-routes.ts +10 -2
  356. package/src/runtime/routes/host-cu-routes.ts +2 -2
  357. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
  358. package/src/runtime/routes/index.ts +8 -0
  359. package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
  360. package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
  361. package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
  362. package/src/runtime/routes/memory-item-routes.ts +8 -3
  363. package/src/runtime/routes/memory-v2-routes.ts +215 -5
  364. package/src/runtime/routes/memory-v3-routes.ts +316 -0
  365. package/src/runtime/routes/migration-routes.ts +21 -24
  366. package/src/runtime/routes/plugins-routes.ts +337 -0
  367. package/src/runtime/routes/rename-conversation-routes.ts +6 -2
  368. package/src/runtime/routes/secret-routes.ts +25 -5
  369. package/src/runtime/routes/settings-routes.ts +12 -11
  370. package/src/runtime/routes/slack-channel-routes.ts +5 -4
  371. package/src/runtime/routes/workspace-routes.ts +25 -10
  372. package/src/runtime/sync/resource-sync-events.ts +106 -38
  373. package/src/runtime/sync/sync-publisher.test.ts +49 -0
  374. package/src/runtime/sync/sync-publisher.ts +2 -1
  375. package/src/runtime/verification-outbound-actions.ts +73 -1
  376. package/src/telemetry/types.ts +12 -0
  377. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  378. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  379. package/src/tools/acp/spawn.test.ts +119 -0
  380. package/src/tools/acp/spawn.ts +15 -2
  381. package/src/tools/apps/definitions.ts +2 -8
  382. package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
  383. package/src/tools/ask-question/ask-question-tool.ts +38 -45
  384. package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
  385. package/src/tools/browser/browser-execution.ts +16 -3
  386. package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
  387. package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
  388. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
  389. package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
  390. package/src/tools/browser/cdp-client/factory.ts +100 -17
  391. package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
  392. package/src/tools/browser/cdp-client/types.ts +65 -0
  393. package/src/tools/browser/pinned-tabs.ts +96 -40
  394. package/src/tools/computer-use/definitions.ts +22 -78
  395. package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
  396. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
  397. package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
  398. package/src/tools/credentials/vault.ts +3 -9
  399. package/src/tools/document/document-tool.ts +59 -0
  400. package/src/tools/execution-target.ts +21 -23
  401. package/src/tools/executor.ts +6 -1
  402. package/src/tools/filesystem/edit.ts +3 -9
  403. package/src/tools/filesystem/list.ts +3 -9
  404. package/src/tools/filesystem/read.ts +3 -9
  405. package/src/tools/filesystem/write.ts +3 -9
  406. package/src/tools/host-filesystem/edit.ts +3 -9
  407. package/src/tools/host-filesystem/read.ts +3 -9
  408. package/src/tools/host-filesystem/transfer.ts +3 -9
  409. package/src/tools/host-filesystem/write.ts +3 -9
  410. package/src/tools/host-terminal/host-shell.ts +3 -9
  411. package/src/tools/mcp/mcp-tool-factory.ts +1 -8
  412. package/src/tools/memory/register.test.ts +1 -1
  413. package/src/tools/memory/register.ts +4 -9
  414. package/src/tools/network/web-fetch.ts +3 -9
  415. package/src/tools/network/web-search.ts +25 -32
  416. package/src/tools/registry.ts +7 -23
  417. package/src/tools/schema-transforms.ts +1 -1
  418. package/src/tools/skills/execute.ts +3 -9
  419. package/src/tools/skills/load.ts +3 -9
  420. package/src/tools/skills/skill-tool-factory.ts +1 -8
  421. package/src/tools/subagent/notify-parent.ts +3 -9
  422. package/src/tools/system/request-permission.ts +3 -9
  423. package/src/tools/terminal/shell.ts +3 -9
  424. package/src/tools/tool-defaults.ts +94 -0
  425. package/src/tools/types.ts +27 -98
  426. package/src/tools/ui-surface/definitions.ts +6 -22
  427. package/src/usage/pricing.ts +23 -0
  428. package/src/usage/types.ts +12 -0
  429. package/src/util/logger.ts +16 -7
  430. package/src/util/platform.ts +7 -2
  431. package/src/util/sqlite3-runtime.ts +65 -0
  432. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
  433. package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
  434. package/src/workspace/migrations/registry.ts +2 -0
  435. package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
  436. package/src/__tests__/message-complete-display-id.test.ts +0 -175
  437. package/src/daemon/query-complexity-router.ts +0 -75
  438. package/src/prompts/cache-boundary.ts +0 -8
@@ -9,7 +9,7 @@ import { and, eq, inArray, sql } from "drizzle-orm";
9
9
 
10
10
  import { getLogger } from "../util/logger.js";
11
11
  import { getDb } from "./db-connection.js";
12
- import { enqueueMemoryJob } from "./jobs-store.js";
12
+ import { enqueueMemoryJob, isMemoryEnabled } from "./jobs-store.js";
13
13
  import { rawGet } from "./raw-query.js";
14
14
  import { memoryCheckpoints, memoryJobs } from "./schema.js";
15
15
 
@@ -20,6 +20,8 @@ const log = getLogger("conversation-starters-cadence");
20
20
  * generating a fresh batch of conversation starters.
21
21
  */
22
22
  export function maybeEnqueueConversationStartersJob(scopeId: string): void {
23
+ if (!isMemoryEnabled()) return;
24
+
23
25
  const db = getDb();
24
26
 
25
27
  // Count total active memory items
@@ -331,10 +331,10 @@ function buildTitlePrompt(
331
331
  }
332
332
 
333
333
  if (userMessage) {
334
- parts.push(`User: ${userMessage}`);
334
+ parts.push(`User: ${stripThinkingTags(userMessage)}`);
335
335
  }
336
336
  if (assistantResponse) {
337
- parts.push(`Assistant: ${assistantResponse}`);
337
+ parts.push(`Assistant: ${stripThinkingTags(assistantResponse)}`);
338
338
  }
339
339
 
340
340
  return parts.join("\n");
@@ -378,12 +378,28 @@ function truncateTitle(title: string): string {
378
378
  function normalizeTitle(raw: string): string {
379
379
  let title = raw.trim().replace(/^["']|["']$/g, "");
380
380
  title = stripMarkdown(title);
381
+ title = stripThinkingTags(title);
381
382
  if (META_FAILURE_TITLES.has(title.toLowerCase())) {
382
383
  return "";
383
384
  }
384
385
  return truncateTitle(title);
385
386
  }
386
387
 
388
+ /** Strip thinking tags so they don't bleed into generated titles. */
389
+ function stripThinkingTags(text: string): string {
390
+ return text
391
+ .replace(/<thinking>[\s\S]*?<\/thinking>/gi, "")
392
+ .replace(/<thought>[\s\S]*?<\/thought>/gi, "")
393
+ .replace(/<think>[\s\S]*?<\/think>/gi, "")
394
+ .replace(/<thought>/gi, "")
395
+ .replace(/<\/thought>/gi, "")
396
+ .replace(/<thinking>/gi, "")
397
+ .replace(/<\/thinking>/gi, "")
398
+ .replace(/<think>/gi, "")
399
+ .replace(/<\/think>/gi, "")
400
+ .replace(/<:[^>]*>/gi, "");
401
+ }
402
+
387
403
  /** Strip common markdown formatting so titles render as plain text. */
388
404
  function stripMarkdown(text: string): string {
389
405
  return text
@@ -458,7 +474,7 @@ function buildRegenerationPrompt(recentMessages: MessageRow[]): string {
458
474
  const text = extractTextForTitle(msg.content);
459
475
  if (!text) continue;
460
476
  const role = msg.role === "user" ? "User" : "Assistant";
461
- parts.push(`${role}: ${text}`);
477
+ parts.push(`${role}: ${stripThinkingTags(text)}`);
462
478
  }
463
479
 
464
480
  return parts.join("\n");
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Run a SQL statement asynchronously, without blocking the daemon's
3
+ * main event loop.
4
+ *
5
+ * `bun:sqlite` is synchronous, so any long-running statement on the
6
+ * shared in-process connection stalls the event loop for the full
7
+ * duration of the statement. For multi-minute operations like `VACUUM`
8
+ * on a multi-GB database, this stalls every other piece of I/O —
9
+ * including the healthz handler — and on platform that has been
10
+ * observed to fail liveness probes and crashloop the pod.
11
+ *
12
+ * Backend selection:
13
+ * 1. **`sqlite3` CLI subprocess (preferred).** Spawn a child process
14
+ * that opens its own SQLite connection, runs the statement, and
15
+ * exits. The daemon's event loop is free for the full duration.
16
+ * SQLite's own file-locking arbitrates between the subprocess and
17
+ * the still-running in-process connection.
18
+ * 2. **In-process `bun:sqlite` (fallback).** Synchronous, blocking.
19
+ * Only fires when no `sqlite3` binary is on the host. This is the
20
+ * same behavior the daemon had before this abstraction existed,
21
+ * and is acceptable on desktop where no liveness probe is going to
22
+ * kill the process for a long stall.
23
+ *
24
+ * Use this for statements known to be slow (`VACUUM`, `ANALYZE`,
25
+ * `PRAGMA optimize`, large bulk `DELETE`/`UPDATE`). Fast queries (a few
26
+ * ms) should keep using the in-process drizzle / `bun:sqlite` handle
27
+ * directly — the subprocess overhead would dominate.
28
+ */
29
+ import { getLogger } from "../util/logger.js";
30
+ import { getDbPath } from "../util/platform.js";
31
+ import { findSqlite3 } from "../util/sqlite3-runtime.js";
32
+ import { getSqlite } from "./db-connection.js";
33
+
34
+ const log = getLogger("db-async-query");
35
+
36
+ /**
37
+ * Default wall-clock cap for an async statement. A real `VACUUM` on a
38
+ * multi-GB database can legitimately take many minutes; the cap is
39
+ * here to bound a runaway subprocess (e.g. one stuck on a stale file
40
+ * lock). Override per call via `runAsyncSqlite(sql, { timeoutMs })`.
41
+ */
42
+ const DEFAULT_TIMEOUT_MS = 60 * 60 * 1000; // 1 hour
43
+
44
+ export type AsyncSqliteBackend = "sqlite3-cli" | "in-process-blocking";
45
+
46
+ export interface AsyncSqliteResult {
47
+ ok: boolean;
48
+ backend: AsyncSqliteBackend;
49
+ error: string | null;
50
+ elapsedMs: number;
51
+ stdout?: string;
52
+ stderr?: string;
53
+ timedOut?: boolean;
54
+ }
55
+
56
+ export interface RunAsyncSqliteOptions {
57
+ /** Override the default 1 h subprocess timeout. */
58
+ timeoutMs?: number;
59
+ /**
60
+ * Force a specific backend. Test-only; production callers should let
61
+ * the runtime pick.
62
+ */
63
+ forceBackend?: AsyncSqliteBackend;
64
+ }
65
+
66
+ let warnedAboutFallback = false;
67
+
68
+ export async function runAsyncSqlite(
69
+ sql: string,
70
+ options: RunAsyncSqliteOptions = {},
71
+ ): Promise<AsyncSqliteResult> {
72
+ const forced = options.forceBackend;
73
+ const sqlite3Path =
74
+ forced === "in-process-blocking" ? undefined : findSqlite3();
75
+
76
+ if (sqlite3Path && forced !== "in-process-blocking") {
77
+ return runViaCli(sqlite3Path, sql, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
78
+ }
79
+
80
+ if (!warnedAboutFallback) {
81
+ warnedAboutFallback = true;
82
+ log.warn(
83
+ "No sqlite3 CLI found on host — falling back to in-process blocking " +
84
+ "execution for slow SQLite statements. Install sqlite3 to keep the " +
85
+ "event loop responsive during VACUUM and other long operations.",
86
+ );
87
+ }
88
+ return runInProcessBlocking(sql);
89
+ }
90
+
91
+ /** For tests: reset the once-only fallback warning. */
92
+ export function _resetFallbackWarning(): void {
93
+ warnedAboutFallback = false;
94
+ }
95
+
96
+ async function runViaCli(
97
+ sqlite3Path: string,
98
+ sql: string,
99
+ timeoutMs: number,
100
+ ): Promise<AsyncSqliteResult> {
101
+ const startMs = Date.now();
102
+ const dbPath = getDbPath();
103
+
104
+ log.info(
105
+ { sqlite3Path, dbPath, timeoutMs, sqlPreview: sql.slice(0, 80) },
106
+ "Running async SQL via sqlite3 CLI subprocess",
107
+ );
108
+
109
+ // Pass the SQL via stdin rather than -cmd so newlines and quoting are
110
+ // never an issue regardless of the statement complexity.
111
+ const proc = Bun.spawn({
112
+ cmd: [sqlite3Path, dbPath],
113
+ stdin: "pipe",
114
+ stdout: "pipe",
115
+ stderr: "pipe",
116
+ });
117
+
118
+ // Write the SQL and close stdin so sqlite3 sees EOF and exits.
119
+ proc.stdin.write(sql + "\n");
120
+ await proc.stdin.end();
121
+
122
+ // Begin draining the streams immediately so the subprocess never
123
+ // blocks on a full pipe buffer.
124
+ const stdoutPromise = new Response(proc.stdout).text();
125
+ const stderrPromise = new Response(proc.stderr).text();
126
+
127
+ let timedOut = false;
128
+ const timer = setTimeout(() => {
129
+ timedOut = true;
130
+ proc.kill("SIGKILL");
131
+ }, timeoutMs);
132
+ if (typeof timer.unref === "function") timer.unref();
133
+
134
+ const exitCode = await proc.exited;
135
+ clearTimeout(timer);
136
+
137
+ const stdout = await stdoutPromise;
138
+ const stderr = await stderrPromise;
139
+ const elapsedMs = Date.now() - startMs;
140
+
141
+ if (timedOut) {
142
+ log.error(
143
+ { timeoutMs, elapsedMs, stderr: stderr.slice(0, 2000) },
144
+ "Async SQL subprocess timed out — killed",
145
+ );
146
+ return {
147
+ ok: false,
148
+ backend: "sqlite3-cli",
149
+ error: `sqlite3 subprocess timed out after ${timeoutMs}ms`,
150
+ elapsedMs,
151
+ stdout,
152
+ stderr,
153
+ timedOut: true,
154
+ };
155
+ }
156
+ if (exitCode !== 0) {
157
+ log.error(
158
+ { exitCode, elapsedMs, stderr: stderr.slice(0, 2000) },
159
+ "Async SQL subprocess failed",
160
+ );
161
+ return {
162
+ ok: false,
163
+ backend: "sqlite3-cli",
164
+ error: `sqlite3 exited with code ${exitCode}: ${stderr.slice(0, 500)}`,
165
+ elapsedMs,
166
+ stdout,
167
+ stderr,
168
+ };
169
+ }
170
+ return {
171
+ ok: true,
172
+ backend: "sqlite3-cli",
173
+ error: null,
174
+ elapsedMs,
175
+ stdout,
176
+ stderr,
177
+ };
178
+ }
179
+
180
+ async function runInProcessBlocking(sql: string): Promise<AsyncSqliteResult> {
181
+ const startMs = Date.now();
182
+ try {
183
+ const sqlite = getSqlite();
184
+ sqlite.exec(sql);
185
+ // Synthesize `stdout` to match what the CLI backend would emit
186
+ // when the caller chained `SELECT changes();` at the end of their
187
+ // SQL. `bun:sqlite`'s `exec()` discards SELECT results, so without
188
+ // this synthesis, callers that read `stdout` to get the row count
189
+ // (the prune jobs in cleanup.ts, for one) would see `undefined`
190
+ // and treat the run as "no rows deleted" — silently dropping the
191
+ // re-enqueue gate on every fallback host. Captured atomically with
192
+ // exec (same synchronous slice — no other code can run between
193
+ // these two lines), so the count is accurate for the SQL we just
194
+ // ran. Harmless for callers that don't read stdout.
195
+ const changes = (
196
+ sqlite.query("SELECT changes() AS c").get() as { c: number }
197
+ ).c;
198
+ return {
199
+ ok: true,
200
+ backend: "in-process-blocking",
201
+ error: null,
202
+ elapsedMs: Date.now() - startMs,
203
+ stdout: `${changes}\n`,
204
+ };
205
+ } catch (err) {
206
+ const message = err instanceof Error ? err.message : String(err);
207
+ return {
208
+ ok: false,
209
+ backend: "in-process-blocking",
210
+ error: message,
211
+ elapsedMs: Date.now() - startMs,
212
+ };
213
+ }
214
+ }
@@ -118,9 +118,11 @@ import {
118
118
  migrateInviteCodeHashColumn,
119
119
  migrateInviteContactId,
120
120
  migrateLlmRequestLogAgentLoopExitReason,
121
+ migrateLlmRequestLogCallSite,
121
122
  migrateLlmRequestLogMessageId,
122
123
  migrateLlmRequestLogProvider,
123
124
  migrateLlmRequestLogsCreatedAtIndex,
125
+ migrateLlmUsageAddRawUsage,
124
126
  migrateLlmUsageAttribution,
125
127
  migrateMemoryGraphImageRefs,
126
128
  migrateMemoryItemSupersession,
@@ -128,6 +130,8 @@ import {
128
130
  migrateMemoryRetrospectiveState,
129
131
  migrateMemoryV2ActivationLogs,
130
132
  migrateMemoryV2InjectionEvents,
133
+ migrateMemoryV3AutoEdges,
134
+ migrateMemoryV3Coactivation,
131
135
  migrateMessageBookmarks,
132
136
  migrateMessagesConversationCreatedAtIndex,
133
137
  migrateMessagesFtsBackfill,
@@ -155,6 +159,7 @@ import {
155
159
  migrateProviderConnectionStatusLabel,
156
160
  migrateReminderRoutingIntent,
157
161
  migrateRemindersToSchedules,
162
+ migrateRenameCleanedAt,
158
163
  migrateRenameConversationTypeColumn,
159
164
  migrateRenameCreatedBySessionIdColumns,
160
165
  migrateRenameFollowupsThreadIdColumn,
@@ -452,6 +457,11 @@ export function initializeDb(): void {
452
457
  migrateStripBaseUrlNonOpenaiCompatible,
453
458
  migrateOnboardingEventsPriorAssistants,
454
459
  migrateConversationCleanedAt,
460
+ migrateRenameCleanedAt,
461
+ migrateLlmUsageAddRawUsage,
462
+ migrateMemoryV3Coactivation,
463
+ migrateMemoryV3AutoEdges,
464
+ migrateLlmRequestLogCallSite,
455
465
  ];
456
466
 
457
467
  // Run each migration step, catching and logging individual failures so one
@@ -3,6 +3,7 @@ import { statSync } from "node:fs";
3
3
  import { getLogger } from "../util/logger.js";
4
4
  import { getDbPath } from "../util/platform.js";
5
5
  import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
6
+ import { runAsyncSqlite } from "./db-async-query.js";
6
7
  import { getSqlite } from "./db-connection.js";
7
8
 
8
9
  const log = getLogger("db-maintenance");
@@ -11,7 +12,7 @@ const DB_MAINTENANCE_CHECKPOINT_KEY = "db_maintenance:last_run";
11
12
  const DB_MAINTENANCE_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
12
13
 
13
14
  interface DbStats {
14
- pageSizeBytes: number;
15
+ pageSize: number;
15
16
  pageCount: number;
16
17
  freelistCount: number;
17
18
  fileSizeBytes: number | null;
@@ -19,7 +20,7 @@ interface DbStats {
19
20
 
20
21
  function getDbStats(): DbStats {
21
22
  const sqlite = getSqlite();
22
- const pageSizeBytes = (
23
+ const pageSize = (
23
24
  sqlite.query("PRAGMA page_size").get() as { page_size: number }
24
25
  ).page_size;
25
26
  const pageCount = (
@@ -34,10 +35,10 @@ function getDbStats(): DbStats {
34
35
  } catch {
35
36
  /* non-fatal */
36
37
  }
37
- return { pageSizeBytes, pageCount, freelistCount, fileSizeBytes };
38
+ return { pageSize, pageCount, freelistCount, fileSizeBytes };
38
39
  }
39
40
 
40
- function runDbMaintenance(): void {
41
+ async function runDbMaintenance(): Promise<void> {
41
42
  const before = getDbStats();
42
43
  const freelistPct =
43
44
  before.pageCount > 0
@@ -54,22 +55,24 @@ function runDbMaintenance(): void {
54
55
  "Starting database maintenance",
55
56
  );
56
57
 
57
- try {
58
- getSqlite().exec("VACUUM");
59
- } catch (err) {
60
- log.warn({ err }, "VACUUM failed (non-fatal)");
61
- try {
62
- getSqlite().exec("PRAGMA optimize");
63
- } catch (optErr) {
64
- log.warn({ err: optErr }, "PRAGMA optimize failed (non-fatal)");
65
- }
66
- return;
58
+ // VACUUM is the long-running one — minutes on a multi-GB DB. PRAGMA
59
+ // optimize is fast but routed through the same async path for
60
+ // consistency and to keep both off the main thread when the CLI
61
+ // backend is available.
62
+ const vacuumResult = await runAsyncSqlite("VACUUM");
63
+ if (!vacuumResult.ok) {
64
+ log.warn(
65
+ { error: vacuumResult.error, backend: vacuumResult.backend },
66
+ "VACUUM failed (non-fatal)",
67
+ );
67
68
  }
68
69
 
69
- try {
70
- getSqlite().exec("PRAGMA optimize");
71
- } catch (err) {
72
- log.warn({ err }, "PRAGMA optimize failed (non-fatal)");
70
+ const optimizeResult = await runAsyncSqlite("PRAGMA optimize");
71
+ if (!optimizeResult.ok) {
72
+ log.warn(
73
+ { error: optimizeResult.error, backend: optimizeResult.backend },
74
+ "PRAGMA optimize failed (non-fatal)",
75
+ );
73
76
  }
74
77
 
75
78
  const after = getDbStats();
@@ -81,17 +84,23 @@ function runDbMaintenance(): void {
81
84
 
82
85
  log.info(
83
86
  {
87
+ backend: vacuumResult.backend,
88
+ vacuumOk: vacuumResult.ok,
89
+ optimizeOk: optimizeResult.ok,
90
+ vacuumElapsedMs: vacuumResult.elapsedMs,
91
+ optimizeElapsedMs: optimizeResult.elapsedMs,
84
92
  beforePageCount: before.pageCount,
85
93
  afterPageCount: after.pageCount,
86
94
  reclaimedPages,
87
- reclaimedBytes,
95
+ beforeFileSizeBytes: before.fileSizeBytes,
88
96
  afterFileSizeBytes: after.fileSizeBytes,
97
+ reclaimedBytes,
89
98
  },
90
99
  "Database maintenance complete",
91
100
  );
92
101
  }
93
102
 
94
- export function maybeRunDbMaintenance(nowMs = Date.now()): void {
103
+ export async function maybeRunDbMaintenance(nowMs = Date.now()): Promise<void> {
95
104
  const lastRun = parseInt(
96
105
  getMemoryCheckpoint(DB_MAINTENANCE_CHECKPOINT_KEY) ?? "0",
97
106
  10,
@@ -99,7 +108,7 @@ export function maybeRunDbMaintenance(nowMs = Date.now()): void {
99
108
  if (nowMs - lastRun < DB_MAINTENANCE_INTERVAL_MS) return;
100
109
 
101
110
  try {
102
- runDbMaintenance();
111
+ await runDbMaintenance();
103
112
  } catch (err) {
104
113
  log.error({ err }, "Database maintenance failed unexpectedly");
105
114
  }
@@ -20,7 +20,11 @@ import { getLogger } from "../../util/logger.js";
20
20
  import { getWorkspaceDir } from "../../util/platform.js";
21
21
  import { getMemoryCheckpoint, setMemoryCheckpoint } from "../checkpoints.js";
22
22
  import { getDb } from "../db-connection.js";
23
- import { enqueueMemoryJob, hasActiveJobOfType } from "../jobs-store.js";
23
+ import {
24
+ enqueueMemoryJob,
25
+ hasActiveJobOfType,
26
+ isMemoryEnabled,
27
+ } from "../jobs-store.js";
24
28
  import { initQdrantClient, resolveQdrantUrl } from "../qdrant-client.js";
25
29
  import { rawAll, rawGet, rawRun } from "../raw-query.js";
26
30
  import { conversations, memoryGraphNodes, memorySegments } from "../schema.js";
@@ -342,6 +346,8 @@ export function maybeEnqueueGraphBootstrap(): void {
342
346
  // Don't enqueue if already in progress
343
347
  if (hasActiveJobOfType("graph_bootstrap")) return;
344
348
 
349
+ if (!isMemoryEnabled()) return;
350
+
345
351
  log.info(
346
352
  { segmentCount, hasJournalFiles },
347
353
  "Graph empty with historical data — enqueueing bootstrap",
@@ -392,6 +398,7 @@ const KIND_TO_PREFIX: Record<string, string> = {
392
398
  */
393
399
  export function migrateToolCreatedItems(): void {
394
400
  if (getMemoryCheckpoint(MIGRATE_ITEMS_CHECKPOINT)) return;
401
+ if (!isMemoryEnabled()) return;
395
402
 
396
403
  const kinds = Object.keys(KIND_TO_PREFIX);
397
404
  const placeholders = kinds.map(() => "?").join(", ");
@@ -23,7 +23,7 @@ import {
23
23
  } from "../../skills/skill-memory.js";
24
24
  import { getLogger } from "../../util/logger.js";
25
25
  import { getDb } from "../db-connection.js";
26
- import { enqueueMemoryJob } from "../jobs-store.js";
26
+ import { enqueueMemoryJob, isMemoryEnabled } from "../jobs-store.js";
27
27
  import { memoryGraphNodes } from "../schema.js";
28
28
  import { createNode } from "./store.js";
29
29
 
@@ -268,7 +268,9 @@ function upsertCapabilityNode(sourceKey: string, content: string): void {
268
268
  })
269
269
  .where(eq(memoryGraphNodes.id, existing.id))
270
270
  .run();
271
- enqueueMemoryJob("embed_graph_node", { nodeId: existing.id });
271
+ if (isMemoryEnabled()) {
272
+ enqueueMemoryJob("embed_graph_node", { nodeId: existing.id });
273
+ }
272
274
  return;
273
275
  }
274
276
 
@@ -301,7 +303,9 @@ function upsertCapabilityNode(sourceKey: string, content: string): void {
301
303
  scopeId: "default",
302
304
  });
303
305
 
304
- enqueueMemoryJob("embed_graph_node", { nodeId: node.id });
306
+ if (isMemoryEnabled()) {
307
+ enqueueMemoryJob("embed_graph_node", { nodeId: node.id });
308
+ }
305
309
  log.info({ sourceKey, nodeId: node.id }, "Created capability graph node");
306
310
  }
307
311
 
@@ -35,6 +35,7 @@ import {
35
35
  type InjectMemoryV2Mode,
36
36
  } from "../v2/injection.js";
37
37
  import { loadNowText } from "../v2/now-text.js";
38
+ import type { RouterTurnPair } from "../v2/router.js";
38
39
  import {
39
40
  loadGraphMemoryState,
40
41
  saveGraphMemoryState,
@@ -440,8 +441,11 @@ export class ConversationGraphMemory {
440
441
  messages,
441
442
  config,
442
443
  "context-load",
444
+ // Context-load runs before the messages array necessarily contains
445
+ // the just-arrived user turn (post-compaction restore, turn 1
446
+ // sketch), so override with the resolved user text rather than
447
+ // walking back through `messages`.
443
448
  rawUserText ?? userQuery ?? "",
444
- "",
445
449
  signal,
446
450
  );
447
451
 
@@ -605,8 +609,7 @@ export class ConversationGraphMemory {
605
609
  messages,
606
610
  config,
607
611
  "per-turn",
608
- userLast,
609
- assistantLast,
612
+ null,
610
613
  signal,
611
614
  );
612
615
  if (v2.routed) {
@@ -762,8 +765,14 @@ export class ConversationGraphMemory {
762
765
  messages: Message[],
763
766
  config: AssistantConfig,
764
767
  mode: InjectMemoryV2Mode,
765
- userMessage: string,
766
- assistantMessage: string,
768
+ /**
769
+ * Override for the just-arrived user message text. Used by
770
+ * `runContextLoad` where the conversation history may not yet contain
771
+ * the user message that triggered the load (e.g. turn 1 / post-
772
+ * compaction restoration). When provided, the extracted pairs array
773
+ * is replaced with `[{ assistantMessage: "", userMessage: override }]`.
774
+ */
775
+ userMessageOverride: string | null,
767
776
  signal: AbortSignal,
768
777
  ): Promise<{
769
778
  routed: boolean;
@@ -776,13 +785,17 @@ export class ConversationGraphMemory {
776
785
 
777
786
  const nowText = await loadNowText(getWorkspaceDir());
778
787
  const currentTurn = this.tracker.getTurn();
788
+ const historicalPairs = config.memory.v2.router.historical_pairs;
789
+ const recentTurnPairs =
790
+ userMessageOverride !== null
791
+ ? [{ assistantMessage: "", userMessage: userMessageOverride }]
792
+ : extractRecentTurnPairs(messages, historicalPairs);
779
793
 
780
794
  const result = await injectMemoryV2Block({
781
795
  database: getDb(),
782
796
  conversationId: this.conversationId,
783
797
  currentTurn,
784
- userMessage,
785
- assistantMessage,
798
+ recentTurnPairs,
786
799
  nowText,
787
800
  messageId: `${this.conversationId}:turn:${currentTurn}`,
788
801
  mode,
@@ -808,16 +821,22 @@ export class ConversationGraphMemory {
808
821
 
809
822
  /**
810
823
  * Count the leading content blocks on a user message that were added by
811
- * `injectMemoryBlock`. Memory-injected images use a 3-block pattern
812
- * (opening `<memory_image>` text + image + closing `</memory_image>` text),
813
- * followed by a `<memory>…</memory>` text block (legacy `<memory __injected>` is also accepted).
814
- * The bare `<memory>` form is matched only when the block also ends with
815
- * `\n</memory>`, so user-authored content that happens to begin with
816
- * `<memory>` (for example, a message discussing the XML-like markup) is not
817
- * mistaken for an injected prefix and stripped on re-injection. A legacy
818
- * 2-block image pattern (no closing tag) is also accepted for backward
819
- * compatibility. The injection prefix is always contiguous at the start,
820
- * so we stop at the first non-memory block.
824
+ * `injectMemoryBlock` or the `memory-v2-static` injector. Memory-injected
825
+ * images use a 3-block pattern (opening `<memory_image>` text + image +
826
+ * closing `</memory_image>` text), followed by a `<memory>…</memory>` text
827
+ * block (legacy `<memory __injected>` is also accepted). The static
828
+ * memory-v2 block uses `<info>…</info>` and is also counted here so that
829
+ * `after-memory-prefix` splices for subsequent injectors (e.g. `now-md`)
830
+ * land after both the dynamic and static blocks.
831
+ *
832
+ * The bare `<memory>` and `<info>` forms are matched only when the block
833
+ * also ends with the corresponding closing tag, so user-authored content
834
+ * that happens to begin with `<memory>` or `<info>` (for example, a
835
+ * message discussing the XML-like markup) is not mistaken for an injected
836
+ * prefix and stripped on re-injection. A legacy 2-block image pattern (no
837
+ * closing tag) is also accepted for backward compatibility. The injection
838
+ * prefix is always contiguous at the start, so we stop at the first
839
+ * non-memory block.
821
840
  */
822
841
  export function countMemoryPrefixBlocks(content: ContentBlock[]): number {
823
842
  let firstNonMemory = 0;
@@ -829,6 +848,8 @@ export function countMemoryPrefixBlocks(content: ContentBlock[]): number {
829
848
  block.type === "text" &&
830
849
  ((block.text.startsWith("<memory>\n") &&
831
850
  block.text.endsWith("\n</memory>")) ||
851
+ (block.text.startsWith("<info>\n") &&
852
+ block.text.endsWith("\n</info>")) ||
832
853
  block.text.startsWith("<memory __injected>\n"))
833
854
  ) {
834
855
  firstNonMemory++;
@@ -991,3 +1012,65 @@ function readRawUserText(message: Message | undefined): string | null {
991
1012
  if (texts.length === 0) return null;
992
1013
  return texts.join(" ");
993
1014
  }
1015
+
1016
+ /**
1017
+ * Walk back through the conversation history and collect the most recent
1018
+ * `K` `(assistant, user)` turn pairs for the router prompt. Each pair
1019
+ * represents the assistant's reply followed by the user message that
1020
+ * came after — the last pair's `userMessage` is the just-arrived turn
1021
+ * that triggered this call.
1022
+ *
1023
+ * Behavior at K=1 is bit-identical to the pre-knob signature: one pair
1024
+ * with the prior assistant reply + the just-arrived user message.
1025
+ *
1026
+ * Edge cases:
1027
+ * - If history has fewer than K full pairs available (e.g. early in a
1028
+ * conversation), returns however many pairs were found, oldest first.
1029
+ * The oldest pair may have `assistantMessage: ""` when there is a
1030
+ * user message with no preceding assistant reply — `runRouterBatch`
1031
+ * skips the `[assistant]:` line in that case.
1032
+ * - Non-text content (tool_use, tool_result, images) is collapsed by
1033
+ * joining all text blocks within a single message with spaces. This
1034
+ * matches the v1-style extraction the router has used since K=1.
1035
+ */
1036
+ function extractRecentTurnPairs(
1037
+ messages: Message[],
1038
+ k: number,
1039
+ ): RouterTurnPair[] {
1040
+ const messageText = (msg: Message): string =>
1041
+ msg.content
1042
+ .filter(
1043
+ (b): b is Extract<typeof b, { type: "text" }> => b.type === "text",
1044
+ )
1045
+ .map((b) => b.text)
1046
+ .join(" ");
1047
+
1048
+ const pairs: RouterTurnPair[] = [];
1049
+ let pendingUser: string | null = null;
1050
+ for (let i = messages.length - 1; i >= 0 && pairs.length < k; i--) {
1051
+ const msg = messages[i];
1052
+ if (msg.role === "user" && pendingUser === null) {
1053
+ pendingUser = messageText(msg);
1054
+ } else if (msg.role === "assistant" && pendingUser !== null) {
1055
+ pairs.unshift({
1056
+ assistantMessage: messageText(msg),
1057
+ userMessage: pendingUser,
1058
+ });
1059
+ pendingUser = null;
1060
+ }
1061
+ }
1062
+ // Conversation start: a user message with no preceding assistant reply
1063
+ // still belongs in the prompt as the just-arrived turn. Emit it with an
1064
+ // empty `assistantMessage` so `runRouterBatch` renders only `[user]:`.
1065
+ if (pendingUser !== null && pairs.length < k) {
1066
+ pairs.unshift({ assistantMessage: "", userMessage: pendingUser });
1067
+ }
1068
+ // Defensive fallback: the router contract requires a non-empty array.
1069
+ // This only fires when `messages` has no user-text content at all
1070
+ // (currently impossible since the agent loop always appends a user
1071
+ // turn before invoking the v2 path, but cheap to keep correct).
1072
+ if (pairs.length === 0) {
1073
+ pairs.push({ assistantMessage: "", userMessage: "" });
1074
+ }
1075
+ return pairs;
1076
+ }