@vellumai/assistant 0.8.3 → 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 (665) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docker-entrypoint.sh +0 -1
  3. package/docs/browser-use-architecture-phase2.md +1 -1
  4. package/knip.json +2 -1
  5. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  6. package/openapi.yaml +1492 -100
  7. package/package.json +1 -1
  8. package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
  9. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +302 -33
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
  14. package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
  15. package/src/__tests__/audit-log-rotation.test.ts +70 -16
  16. package/src/__tests__/background-workers-disk-pressure.test.ts +4 -3
  17. package/src/__tests__/btw-routes.test.ts +2 -3
  18. package/src/__tests__/call-controller.test.ts +0 -1
  19. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  20. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  21. package/src/__tests__/channel-guardian.test.ts +3 -3
  22. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  23. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  24. package/src/__tests__/checker.test.ts +6 -15
  25. package/src/__tests__/compaction-events.test.ts +2 -1
  26. package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
  27. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  28. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
  29. package/src/__tests__/computer-use-tools.test.ts +2 -4
  30. package/src/__tests__/config-watcher.test.ts +1 -1
  31. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  32. package/src/__tests__/context-token-estimator.test.ts +91 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  34. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
  35. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +55 -4
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +228 -8
  37. package/src/__tests__/conversation-agent-loop.test.ts +188 -129
  38. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
  39. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  40. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  41. package/src/__tests__/conversation-clear-safety.test.ts +25 -25
  42. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  43. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
  44. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  45. package/src/__tests__/conversation-error.test.ts +31 -0
  46. package/src/__tests__/conversation-fork-crud.test.ts +324 -0
  47. package/src/__tests__/conversation-lifecycle.test.ts +53 -12
  48. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  49. package/src/__tests__/conversation-load-history-stripped.test.ts +279 -0
  50. package/src/__tests__/conversation-pairing.test.ts +2 -2
  51. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  52. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -1
  53. package/src/__tests__/conversation-queue.test.ts +1 -1
  54. package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
  55. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
  57. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  58. package/src/__tests__/conversation-skill-tools.test.ts +2 -5
  59. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  60. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  61. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  62. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  63. package/src/__tests__/conversation-store.test.ts +1 -1
  64. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  65. package/src/__tests__/conversation-sync-tags.test.ts +99 -32
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -1
  67. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  69. package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
  70. package/src/__tests__/credential-execution-tools.test.ts +6 -6
  71. package/src/__tests__/credential-security-invariants.test.ts +7 -0
  72. package/src/__tests__/credential-vault-unit.test.ts +2 -2
  73. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  74. package/src/__tests__/dm-backfill.test.ts +64 -0
  75. package/src/__tests__/dm-persistence.test.ts +33 -0
  76. package/src/__tests__/document-find-replace.test.ts +501 -0
  77. package/src/__tests__/dynamic-page-surface.test.ts +2 -2
  78. package/src/__tests__/email-html-renderer.test.ts +12 -0
  79. package/src/__tests__/first-greeting.test.ts +23 -2
  80. package/src/__tests__/gateway-flag-listener.test.ts +237 -0
  81. package/src/__tests__/gemini-provider.test.ts +78 -0
  82. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  83. package/src/__tests__/guardian-outbound-http.test.ts +7 -5
  84. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  85. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  87. package/src/__tests__/heartbeat-service.test.ts +4 -0
  88. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  89. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  90. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  91. package/src/__tests__/host-file-proxy.test.ts +8 -1
  92. package/src/__tests__/host-shell-tool.test.ts +1 -1
  93. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  94. package/src/__tests__/identity-routes.test.ts +57 -0
  95. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  96. package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
  97. package/src/__tests__/injector-chain.test.ts +2 -0
  98. package/src/__tests__/injector-document-comments.test.ts +378 -0
  99. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  100. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  102. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  103. package/src/__tests__/list-messages-tool-merge.test.ts +77 -17
  104. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  105. package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
  106. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
  107. package/src/__tests__/llm-resolver.test.ts +161 -9
  108. package/src/__tests__/llm-usage-store.test.ts +66 -0
  109. package/src/__tests__/log-export-routes.test.ts +99 -2
  110. package/src/__tests__/logger.test.ts +89 -0
  111. package/src/__tests__/mcp-abort-signal.test.ts +2 -2
  112. package/src/__tests__/media-generate-image.test.ts +31 -0
  113. package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
  114. package/src/__tests__/message-queue-steer.test.ts +114 -0
  115. package/src/__tests__/model-intents.test.ts +2 -4
  116. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  117. package/src/__tests__/onboarding-template-contract.test.ts +1 -1
  118. package/src/__tests__/openai-provider.test.ts +151 -0
  119. package/src/__tests__/openai-responses-provider.test.ts +118 -16
  120. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  121. package/src/__tests__/pending-interactions-resolved-event.test.ts +189 -0
  122. package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
  123. package/src/__tests__/platform.test.ts +2 -5
  124. package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
  125. package/src/__tests__/plugin-bootstrap.test.ts +2 -2
  126. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  127. package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
  128. package/src/__tests__/plugin-types.test.ts +3 -2
  129. package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
  130. package/src/__tests__/pricing.test.ts +12 -0
  131. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  132. package/src/__tests__/process-message-display-content.test.ts +21 -16
  133. package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
  134. package/src/__tests__/registry.test.ts +2 -8
  135. package/src/__tests__/require-fresh-approval.test.ts +2 -2
  136. package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
  137. package/src/__tests__/server-history-render.test.ts +83 -4
  138. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  139. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  140. package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
  141. package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
  142. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  143. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  144. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  145. package/src/__tests__/suggestion-routes.test.ts +1 -0
  146. package/src/__tests__/sync-message-contract.test.ts +59 -0
  147. package/src/__tests__/system-prompt.test.ts +161 -124
  148. package/src/__tests__/terminal-tools.test.ts +12 -2
  149. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  150. package/src/__tests__/thread-backfill.test.ts +370 -22
  151. package/src/__tests__/tool-approval-handler.test.ts +1 -5
  152. package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
  153. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
  154. package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
  155. package/src/__tests__/tool-executor.test.ts +89 -53
  156. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
  157. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  158. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  159. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
  160. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  161. package/src/__tests__/twilio-routes.test.ts +1 -1
  162. package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
  163. package/src/__tests__/usage-routes.test.ts +3 -0
  164. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  165. package/src/__tests__/web-fetch.test.ts +2 -2
  166. package/src/__tests__/workspace-git-service.test.ts +94 -10
  167. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  168. package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
  169. package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
  170. package/src/acp/prepare-agent-env.ts +78 -0
  171. package/src/acp/session-manager.ts +1 -1
  172. package/src/agent/attachments.ts +1 -0
  173. package/src/agent/loop.ts +65 -20
  174. package/src/api/README.md +5 -0
  175. package/src/api/index.ts +4 -0
  176. package/src/api/package.json +10 -0
  177. package/src/background-wake/background-wake-routes.test.ts +233 -0
  178. package/src/background-wake/next-wake.test.ts +289 -0
  179. package/src/background-wake/next-wake.ts +172 -0
  180. package/src/background-wake/runtime-registry.ts +24 -0
  181. package/src/browser/operations.ts +15 -0
  182. package/src/cli/commands/__tests__/browser.test.ts +23 -5
  183. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  184. package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
  185. package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
  186. package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
  187. package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
  188. package/src/cli/commands/__tests__/memory-v2.test.ts +10 -12
  189. package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
  190. package/src/cli/commands/browser.ts +247 -0
  191. package/src/cli/commands/conversations.ts +128 -1
  192. package/src/cli/commands/domain.ts +91 -41
  193. package/src/cli/commands/inference-providers.ts +147 -1
  194. package/src/cli/commands/inference.ts +93 -40
  195. package/src/cli/commands/memory-v2-compare-render.ts +115 -0
  196. package/src/cli/commands/memory-v2.ts +483 -0
  197. package/src/cli/commands/memory-v3-render.ts +344 -0
  198. package/src/cli/commands/memory-v3.ts +316 -0
  199. package/src/cli/commands/notifications.ts +24 -2
  200. package/src/cli/program.ts +2 -0
  201. package/src/cli/utils/conversation-id.ts +17 -5
  202. package/src/config/assistant-feature-flags.ts +21 -9
  203. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  204. package/src/config/bundled-skills/document-editor/SKILL.md +124 -0
  205. package/src/config/bundled-skills/document-editor/TOOLS.json +258 -0
  206. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  207. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  208. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  209. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  210. package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
  211. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  212. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  213. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  214. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  215. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
  216. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
  217. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
  218. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
  219. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
  220. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  221. package/src/config/bundled-tool-registry.ts +24 -12
  222. package/src/config/call-site-defaults.ts +20 -0
  223. package/src/config/feature-flag-registry.json +115 -3
  224. package/src/config/llm-resolver.ts +16 -2
  225. package/src/config/schemas/__tests__/memory-v2.test.ts +217 -1
  226. package/src/config/schemas/call-site-catalog.ts +35 -0
  227. package/src/config/schemas/llm.ts +14 -0
  228. package/src/config/schemas/memory-v2.ts +294 -1
  229. package/src/config/schemas/memory.ts +2 -1
  230. package/src/context/compactor.ts +60 -1
  231. package/src/context/token-estimator.ts +47 -4
  232. package/src/context/window-manager.ts +25 -0
  233. package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
  234. package/src/conversations/message-consolidation.ts +404 -0
  235. package/src/credential-health/credential-health-service.ts +34 -19
  236. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
  237. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  238. package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
  239. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  240. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  241. package/src/daemon/conversation-agent-loop-handlers.ts +155 -36
  242. package/src/daemon/conversation-agent-loop.ts +307 -88
  243. package/src/daemon/conversation-error.ts +31 -1
  244. package/src/daemon/conversation-lifecycle.ts +149 -118
  245. package/src/daemon/conversation-messaging.ts +3 -0
  246. package/src/daemon/conversation-process.ts +273 -0
  247. package/src/daemon/conversation-queue-manager.ts +14 -0
  248. package/src/daemon/conversation-runtime-assembly.ts +145 -84
  249. package/src/daemon/conversation-slash.ts +37 -5
  250. package/src/daemon/conversation-surfaces.ts +45 -2
  251. package/src/daemon/conversation-tool-setup.ts +70 -3
  252. package/src/daemon/conversation-usage.ts +2 -0
  253. package/src/daemon/conversation.ts +54 -32
  254. package/src/daemon/disk-pressure-guard.ts +14 -2
  255. package/src/daemon/first-greeting.ts +10 -0
  256. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  257. package/src/daemon/handlers/config-a2a.ts +160 -0
  258. package/src/daemon/handlers/config-model.test.ts +2 -0
  259. package/src/daemon/handlers/conversations.ts +90 -3
  260. package/src/daemon/handlers/shared.ts +92 -29
  261. package/src/daemon/host-bash-proxy.ts +1 -1
  262. package/src/daemon/host-browser-proxy.ts +5 -5
  263. package/src/daemon/host-cu-proxy.ts +5 -5
  264. package/src/daemon/host-file-proxy.ts +5 -5
  265. package/src/daemon/host-proxy-base.ts +4 -4
  266. package/src/daemon/host-transfer-proxy.ts +11 -11
  267. package/src/daemon/lifecycle.ts +40 -23
  268. package/src/daemon/meet-manifest-loader.ts +1 -7
  269. package/src/daemon/message-protocol.ts +4 -0
  270. package/src/daemon/message-types/conversations.ts +14 -9
  271. package/src/daemon/message-types/document-comments.ts +50 -0
  272. package/src/daemon/message-types/home.ts +1 -13
  273. package/src/daemon/message-types/messages.ts +66 -7
  274. package/src/daemon/message-types/surfaces.ts +3 -1
  275. package/src/daemon/message-types/sync.ts +14 -0
  276. package/src/daemon/message-types/web-activity.ts +57 -0
  277. package/src/daemon/plugin-source-watcher.ts +135 -3
  278. package/src/daemon/process-message.ts +69 -12
  279. package/src/daemon/shutdown-handlers.ts +24 -5
  280. package/src/daemon/switch-inference-profile-tool.ts +52 -0
  281. package/src/daemon/tool-setup-types.ts +13 -0
  282. package/src/daemon/trust-context.ts +6 -0
  283. package/src/documents/document-comments-store.test.ts +338 -0
  284. package/src/documents/document-comments-store.ts +237 -0
  285. package/src/documents/document-store.ts +202 -0
  286. package/src/events/relationship-state-updated.ts +25 -0
  287. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -2
  288. package/src/heartbeat/heartbeat-service.ts +1 -0
  289. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  290. package/src/home/feed-types.ts +6 -1
  291. package/src/home/home-content-refresh.ts +52 -0
  292. package/src/home/home-greeting-cache.ts +69 -0
  293. package/src/home/home-greeting.ts +85 -0
  294. package/src/home/suggested-prompts.ts +168 -9
  295. package/src/ipc/gateway-flag-listener.ts +123 -0
  296. package/src/ipc/skill-routes/registries.ts +8 -12
  297. package/src/memory/__tests__/db-async-query.test.ts +165 -0
  298. package/src/memory/__tests__/db-maintenance.test.ts +115 -0
  299. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
  300. package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
  301. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  302. package/src/memory/__tests__/memory-retrospective-job.test.ts +327 -6
  303. package/src/memory/auto-analysis-enqueue.ts +5 -1
  304. package/src/memory/conversation-crud.ts +191 -100
  305. package/src/memory/conversation-starters-cadence.ts +3 -1
  306. package/src/memory/conversation-title-service.ts +19 -3
  307. package/src/memory/db-async-query.ts +214 -0
  308. package/src/memory/db-init.ts +26 -0
  309. package/src/memory/db-maintenance.ts +30 -21
  310. package/src/memory/delivery-crud.ts +41 -0
  311. package/src/memory/delivery-status.ts +141 -15
  312. package/src/memory/external-conversation-store.ts +32 -1
  313. package/src/memory/graph/bootstrap.ts +8 -1
  314. package/src/memory/graph/capability-seed.ts +7 -3
  315. package/src/memory/graph/conversation-graph-memory.ts +100 -17
  316. package/src/memory/graph/extraction.ts +1 -5
  317. package/src/memory/graph/graph-search.ts +7 -1
  318. package/src/memory/indexer.ts +28 -18
  319. package/src/memory/job-handlers/cleanup.ts +76 -18
  320. package/src/memory/job-handlers/conversation-starters.ts +1 -4
  321. package/src/memory/jobs/embed-pkb-file.ts +6 -1
  322. package/src/memory/jobs-store.ts +14 -0
  323. package/src/memory/jobs-worker.ts +68 -15
  324. package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
  325. package/src/memory/llm-request-log-source-local.ts +7 -0
  326. package/src/memory/llm-request-log-source.ts +9 -2
  327. package/src/memory/llm-request-log-store.ts +43 -1
  328. package/src/memory/llm-usage-store.ts +24 -0
  329. package/src/memory/memory-retrospective-constants.ts +28 -0
  330. package/src/memory/memory-retrospective-enqueue.ts +11 -3
  331. package/src/memory/memory-retrospective-job.ts +413 -18
  332. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  333. package/src/memory/memory-v2-activation-log-store.ts +41 -14
  334. package/src/memory/migrations/100-core-tables.ts +1 -0
  335. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  336. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  337. package/src/memory/migrations/253-document-comments.ts +47 -0
  338. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  339. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  340. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  341. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  342. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  343. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  344. package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
  345. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
  346. package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
  347. package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
  348. package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
  349. package/src/memory/migrations/index.ts +34 -0
  350. package/src/memory/migrations/registry.ts +58 -0
  351. package/src/memory/onboarding-events-store.ts +7 -0
  352. package/src/memory/schema/calls.ts +1 -0
  353. package/src/memory/schema/conversations.ts +3 -0
  354. package/src/memory/schema/infrastructure.ts +22 -0
  355. package/src/memory/tool-usage-store.ts +36 -8
  356. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
  357. package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
  358. package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
  359. package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
  362. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  363. package/src/memory/v2/__tests__/injection.test.ts +158 -112
  364. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  365. package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
  366. package/src/memory/v2/__tests__/router.test.ts +660 -4
  367. package/src/memory/v2/consolidation-job.ts +14 -0
  368. package/src/memory/v2/harness/compare.ts +57 -0
  369. package/src/memory/v2/harness/metrics.ts +124 -0
  370. package/src/memory/v2/harness/oracle.ts +145 -0
  371. package/src/memory/v2/harness/replay-input.ts +224 -0
  372. package/src/memory/v2/harness/retriever.ts +74 -0
  373. package/src/memory/v2/harness/router-retriever.ts +43 -0
  374. package/src/memory/v2/harness/runner.ts +106 -0
  375. package/src/memory/v2/harness/trace.ts +58 -0
  376. package/src/memory/v2/injection-events.ts +101 -0
  377. package/src/memory/v2/injection.ts +42 -25
  378. package/src/memory/v2/page-index.ts +209 -7
  379. package/src/memory/v2/page-store.ts +18 -0
  380. package/src/memory/v2/prompts/router.ts +26 -1
  381. package/src/memory/v2/qdrant.ts +14 -2
  382. package/src/memory/v2/router.ts +369 -62
  383. package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
  384. package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
  385. package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
  386. package/src/memory/v3/__tests__/edges.test.ts +563 -0
  387. package/src/memory/v3/__tests__/filter.test.ts +512 -0
  388. package/src/memory/v3/__tests__/gate.test.ts +574 -0
  389. package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
  390. package/src/memory/v3/__tests__/loop.test.ts +530 -0
  391. package/src/memory/v3/__tests__/retriever.test.ts +226 -0
  392. package/src/memory/v3/__tests__/scouts.test.ts +440 -0
  393. package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
  394. package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
  395. package/src/memory/v3/__tests__/traversal.test.ts +469 -0
  396. package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
  397. package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
  398. package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
  399. package/src/memory/v3/__tests__/validate.test.ts +245 -0
  400. package/src/memory/v3/auto-edges.ts +223 -0
  401. package/src/memory/v3/coactivation-store.ts +124 -0
  402. package/src/memory/v3/consolidation-job.ts +323 -0
  403. package/src/memory/v3/edge-learning-job.ts +160 -0
  404. package/src/memory/v3/edges.ts +249 -0
  405. package/src/memory/v3/filter.ts +281 -0
  406. package/src/memory/v3/gate.ts +334 -0
  407. package/src/memory/v3/index-composition.ts +113 -0
  408. package/src/memory/v3/llm-capture.ts +46 -0
  409. package/src/memory/v3/loop.ts +382 -0
  410. package/src/memory/v3/maintenance.ts +144 -0
  411. package/src/memory/v3/prompt-context.ts +33 -0
  412. package/src/memory/v3/prompts/consolidation.ts +458 -0
  413. package/src/memory/v3/prompts/system-prompts.ts +196 -0
  414. package/src/memory/v3/retriever.ts +33 -0
  415. package/src/memory/v3/scouts.ts +420 -0
  416. package/src/memory/v3/shadow-middleware.ts +305 -0
  417. package/src/memory/v3/traversal.ts +206 -0
  418. package/src/memory/v3/tree-index.ts +237 -0
  419. package/src/memory/v3/tree-store.ts +394 -0
  420. package/src/memory/v3/tree-walk.ts +351 -0
  421. package/src/memory/v3/types.ts +65 -0
  422. package/src/memory/v3/validate.ts +300 -0
  423. package/src/messaging/providers/index.ts +7 -1
  424. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  425. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  426. package/src/messaging/providers/slack/adapter.ts +178 -25
  427. package/src/messaging/providers/slack/api.test.ts +54 -0
  428. package/src/messaging/providers/slack/api.ts +119 -3
  429. package/src/messaging/providers/slack/client.ts +12 -0
  430. package/src/messaging/providers/slack/deep-link.ts +20 -1
  431. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  432. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  433. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  434. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  435. package/src/messaging/providers/slack/send.test.ts +77 -0
  436. package/src/messaging/providers/slack/send.ts +8 -2
  437. package/src/messaging/providers/slack/types.ts +14 -0
  438. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
  439. package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
  440. package/src/notifications/adapters/macos.ts +18 -1
  441. package/src/notifications/adapters/platform.ts +1 -1
  442. package/src/notifications/conversation-seed-composer.ts +14 -2
  443. package/src/notifications/decision-engine.ts +1 -4
  444. package/src/notifications/deferred-emit.ts +135 -0
  445. package/src/notifications/emit-signal.ts +38 -50
  446. package/src/notifications/home-feed-side-effect.ts +60 -30
  447. package/src/oauth/connect-orchestrator.ts +3 -0
  448. package/src/oauth/credential-token-resolver.ts +2 -0
  449. package/src/oauth/manual-token-connection.ts +19 -0
  450. package/src/oauth/oauth-store.ts +12 -0
  451. package/src/oauth/seed-providers.ts +22 -0
  452. package/src/permissions/prompter.ts +8 -5
  453. package/src/permissions/question-prompter.ts +5 -2
  454. package/src/permissions/secret-prompter.ts +6 -3
  455. package/src/plugin-api/index.ts +4 -0
  456. package/src/plugin-api/types.ts +7 -33
  457. package/src/plugins/defaults/index.ts +6 -0
  458. package/src/plugins/defaults/injectors.ts +100 -20
  459. package/src/plugins/external-plugin-loader.ts +5 -68
  460. package/src/plugins/types.ts +11 -16
  461. package/src/proactive-artifact/aux-message-injector.ts +17 -4
  462. package/src/prompts/__tests__/system-prompt.test.ts +46 -2
  463. package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
  464. package/src/prompts/normalize-onboarding.ts +40 -0
  465. package/src/prompts/persona-resolver.ts +36 -21
  466. package/src/prompts/sections.ts +69 -19
  467. package/src/prompts/system-prompt.ts +118 -216
  468. package/src/prompts/template-detection.ts +37 -0
  469. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  470. package/src/prompts/templates/BOOTSTRAP.md +10 -2
  471. package/src/prompts/templates/VOICE.md +3 -0
  472. package/src/prompts/templates/system-sections.ts +281 -9
  473. package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
  474. package/src/providers/__tests__/retry-callsite.test.ts +85 -5
  475. package/src/providers/anthropic/client.ts +159 -66
  476. package/src/providers/call-site-routing.ts +14 -2
  477. package/src/providers/connection-model-compat.ts +38 -0
  478. package/src/providers/connection-resolution.ts +16 -2
  479. package/src/providers/fireworks/client.ts +20 -2
  480. package/src/providers/gemini/client.ts +49 -6
  481. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  482. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  483. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  484. package/src/providers/inference/adapter-factory.ts +18 -1
  485. package/src/providers/inference/auth.ts +3 -3
  486. package/src/providers/inference/codex-token-refresh.ts +128 -0
  487. package/src/providers/inference/resolve-auth.ts +49 -6
  488. package/src/providers/minimax/client.ts +106 -0
  489. package/src/providers/model-catalog.ts +91 -1
  490. package/src/providers/model-intents.ts +1 -1
  491. package/src/providers/openai/chat-completions-provider.ts +63 -23
  492. package/src/providers/openai/codex-models.ts +18 -0
  493. package/src/providers/openai/responses-provider.ts +86 -23
  494. package/src/providers/openrouter/client.ts +5 -1
  495. package/src/providers/provider-send-message.ts +7 -1
  496. package/src/providers/retry.ts +34 -3
  497. package/src/providers/thinking-config.ts +26 -1
  498. package/src/providers/types.ts +25 -0
  499. package/src/providers/usage-tracking.ts +2 -0
  500. package/src/runtime/AGENTS.md +2 -2
  501. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  502. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  503. package/src/runtime/agent-wake.ts +152 -56
  504. package/src/runtime/assistant-event-hub.ts +76 -6
  505. package/src/runtime/auth/route-policy.ts +43 -3
  506. package/src/runtime/background-job-runner.ts +26 -0
  507. package/src/runtime/btw-sidechain.ts +0 -6
  508. package/src/runtime/channel-reply-delivery.ts +182 -47
  509. package/src/runtime/channel-retry-sweep.ts +141 -16
  510. package/src/runtime/http-types.ts +7 -6
  511. package/src/runtime/migrations/vbundle-builder.ts +10 -3
  512. package/src/runtime/pending-interactions.ts +50 -8
  513. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  514. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +161 -1
  515. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  516. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +290 -0
  517. package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
  518. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  519. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  520. package/src/runtime/routes/acp-routes.test.ts +255 -6
  521. package/src/runtime/routes/acp-routes.ts +8 -1
  522. package/src/runtime/routes/approval-routes.ts +4 -1
  523. package/src/runtime/routes/avatar-routes.ts +10 -10
  524. package/src/runtime/routes/background-wake-routes.ts +188 -0
  525. package/src/runtime/routes/browser-tabs-routes.ts +200 -0
  526. package/src/runtime/routes/btw-routes.ts +0 -6
  527. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  528. package/src/runtime/routes/content-source-routes.ts +78 -0
  529. package/src/runtime/routes/conversation-cli-routes.ts +147 -2
  530. package/src/runtime/routes/conversation-list-routes.ts +12 -4
  531. package/src/runtime/routes/conversation-management-routes.ts +77 -20
  532. package/src/runtime/routes/conversation-query-routes.ts +196 -31
  533. package/src/runtime/routes/conversation-routes.ts +472 -425
  534. package/src/runtime/routes/conversation-starter-routes.ts +6 -3
  535. package/src/runtime/routes/disk-pressure-routes.ts +1 -1
  536. package/src/runtime/routes/document-comments-routes.ts +287 -0
  537. package/src/runtime/routes/documents-routes.ts +33 -0
  538. package/src/runtime/routes/domain-routes.ts +60 -10
  539. package/src/runtime/routes/email-routes.ts +5 -2
  540. package/src/runtime/routes/events-routes.ts +54 -10
  541. package/src/runtime/routes/group-routes.ts +24 -8
  542. package/src/runtime/routes/home-feed-routes.ts +6 -3
  543. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  544. package/src/runtime/routes/host-browser-routes.ts +17 -2
  545. package/src/runtime/routes/host-cu-routes.ts +2 -2
  546. package/src/runtime/routes/identity-routes.ts +21 -0
  547. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  548. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
  549. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  550. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  551. package/src/runtime/routes/index.ts +20 -4
  552. package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
  553. package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
  554. package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
  555. package/src/runtime/routes/integrations/a2a.ts +60 -1
  556. package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
  557. package/src/runtime/routes/log-export-routes.ts +39 -0
  558. package/src/runtime/routes/memory-item-routes.ts +8 -3
  559. package/src/runtime/routes/memory-v2-routes.ts +427 -0
  560. package/src/runtime/routes/memory-v3-routes.ts +316 -0
  561. package/src/runtime/routes/migration-routes.ts +21 -24
  562. package/src/runtime/routes/notification-routes.ts +19 -2
  563. package/src/runtime/routes/plugins-routes.ts +337 -0
  564. package/src/runtime/routes/question-routes.ts +4 -1
  565. package/src/runtime/routes/rename-conversation-routes.ts +6 -2
  566. package/src/runtime/routes/sanity-routes.ts +159 -0
  567. package/src/runtime/routes/secret-routes.ts +25 -5
  568. package/src/runtime/routes/settings-routes.ts +12 -11
  569. package/src/runtime/routes/slack-channel-routes.ts +188 -0
  570. package/src/runtime/routes/workspace-routes.ts +25 -10
  571. package/src/runtime/services/conversation-serializer.ts +30 -4
  572. package/src/runtime/sync/resource-sync-events.ts +106 -38
  573. package/src/runtime/sync/sync-publisher.test.ts +49 -0
  574. package/src/runtime/sync/sync-publisher.ts +2 -1
  575. package/src/runtime/verification-outbound-actions.ts +73 -1
  576. package/src/schedule/integration-status.ts +3 -1
  577. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  578. package/src/security/oauth2-device-code.ts +307 -0
  579. package/src/security/oauth2.ts +26 -9
  580. package/src/security/secure-keys.ts +5 -0
  581. package/src/skills/catalog-install.ts +6 -2
  582. package/src/telemetry/types.ts +12 -0
  583. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  584. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  585. package/src/tools/acp/spawn.test.ts +119 -0
  586. package/src/tools/acp/spawn.ts +15 -2
  587. package/src/tools/apps/definitions.ts +2 -8
  588. package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
  589. package/src/tools/ask-question/ask-question-tool.ts +38 -45
  590. package/src/tools/browser/__tests__/pinned-tabs.test.ts +150 -0
  591. package/src/tools/browser/browser-execution.ts +106 -0
  592. package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
  593. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  594. package/src/tools/browser/cdp-client/__tests__/types.test.ts +4 -0
  595. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +22 -0
  596. package/src/tools/browser/cdp-client/extension-cdp-client.ts +42 -2
  597. package/src/tools/browser/cdp-client/factory.ts +171 -4
  598. package/src/tools/browser/cdp-client/local-cdp-client.ts +21 -0
  599. package/src/tools/browser/cdp-client/types.ts +101 -0
  600. package/src/tools/browser/pinned-tabs.ts +146 -0
  601. package/src/tools/computer-use/definitions.ts +22 -78
  602. package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
  603. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
  604. package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
  605. package/src/tools/credentials/vault.ts +3 -9
  606. package/src/tools/document/document-comment-tool.test.ts +379 -0
  607. package/src/tools/document/document-comment-tool.ts +156 -0
  608. package/src/tools/document/document-tool.ts +187 -2
  609. package/src/tools/execution-target.ts +21 -23
  610. package/src/tools/executor.ts +6 -1
  611. package/src/tools/filesystem/edit.ts +3 -9
  612. package/src/tools/filesystem/list.ts +3 -9
  613. package/src/tools/filesystem/read.ts +3 -9
  614. package/src/tools/filesystem/write.ts +3 -9
  615. package/src/tools/host-filesystem/edit.ts +3 -9
  616. package/src/tools/host-filesystem/read.ts +3 -9
  617. package/src/tools/host-filesystem/transfer.ts +3 -9
  618. package/src/tools/host-filesystem/write.ts +3 -9
  619. package/src/tools/host-terminal/host-shell.ts +3 -9
  620. package/src/tools/mcp/mcp-tool-factory.ts +1 -8
  621. package/src/tools/memory/register.test.ts +1 -1
  622. package/src/tools/memory/register.ts +4 -9
  623. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  624. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  625. package/src/tools/network/domain-normalize.ts +17 -0
  626. package/src/tools/network/web-fetch.ts +216 -73
  627. package/src/tools/network/web-search.ts +216 -98
  628. package/src/tools/registry.ts +7 -23
  629. package/src/tools/schema-transforms.ts +1 -1
  630. package/src/tools/skills/execute.ts +3 -9
  631. package/src/tools/skills/load.ts +3 -9
  632. package/src/tools/skills/skill-tool-factory.ts +1 -8
  633. package/src/tools/subagent/notify-parent.ts +3 -9
  634. package/src/tools/system/request-permission.ts +3 -9
  635. package/src/tools/terminal/safe-env.ts +3 -2
  636. package/src/tools/terminal/shell.ts +3 -9
  637. package/src/tools/tool-approval-handler.ts +19 -12
  638. package/src/tools/tool-defaults.ts +94 -0
  639. package/src/tools/types.ts +31 -98
  640. package/src/tools/ui-surface/definitions.ts +9 -23
  641. package/src/types/onboarding-context.ts +4 -0
  642. package/src/usage/pricing.ts +23 -0
  643. package/src/usage/types.ts +12 -0
  644. package/src/util/__tests__/favicon.test.ts +84 -0
  645. package/src/util/favicon.ts +40 -0
  646. package/src/util/logger.ts +16 -7
  647. package/src/util/platform.ts +7 -7
  648. package/src/util/sqlite3-runtime.ts +65 -0
  649. package/src/workspace/git-service.ts +75 -4
  650. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
  651. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  652. package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
  653. package/src/workspace/migrations/registry.ts +4 -0
  654. package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
  655. package/src/__tests__/message-complete-display-id.test.ts +0 -175
  656. package/src/config/bundled-skills/document/SKILL.md +0 -54
  657. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  658. package/src/daemon/seed-files.ts +0 -18
  659. package/src/prompts/cache-boundary.ts +0 -8
  660. package/src/runtime/routes/interface-routes.ts +0 -43
  661. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  662. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  663. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  664. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  665. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -6,11 +6,22 @@
6
6
  *
7
7
  * Two sources of prompts:
8
8
  * - **Deterministic** — derived from missing OAuth connections.
9
+ * Computed inline (read-only, safe for GET).
9
10
  * - **Assistant-generated** — contextual suggestions from the LLM
10
- * (placeholder; not yet implemented).
11
+ * based on what's relevant to the user. Read from an in-memory
12
+ * cache in the GET path; generation runs in the background via
13
+ * `refreshAssistantSuggestedPrompts`.
11
14
  */
12
15
 
13
- import { isProviderConnected, listProviders } from "../oauth/oauth-store.js";
16
+ import { resolveCallSiteConfig } from "../config/llm-resolver.js";
17
+ import { getConfig } from "../config/loader.js";
18
+ import { listProviders } from "../oauth/oauth-store.js";
19
+ import { buildSystemPrompt } from "../prompts/system-prompt.js";
20
+ import { getConfiguredProvider } from "../providers/provider-send-message.js";
21
+ import { buildAssistantEvent } from "../runtime/assistant-event.js";
22
+ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
23
+ import { runBtwSidechain } from "../runtime/btw-sidechain.js";
24
+ import { isOAuthProviderConnected } from "../schedule/integration-status.js";
14
25
  import { getLogger } from "../util/logger.js";
15
26
  import type { SuggestedPrompt } from "./feed-types.js";
16
27
 
@@ -72,27 +83,88 @@ const CONNECT_PROMPT_META: Record<
72
83
  },
73
84
  };
74
85
 
86
+ const LLM_SUGGESTIONS_TIMEOUT_MS = 5_000;
87
+ const LLM_CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // In-memory cache for LLM-generated suggestions
91
+ // ---------------------------------------------------------------------------
92
+
93
+ let cachedLLMPrompts: SuggestedPrompt[] = [];
94
+ let cachedLLMPromptsAt = 0;
95
+
75
96
  /**
76
- * Produce deterministic suggested prompts based on missing OAuth
77
- * connections and (in the future) assistant-generated conversation
78
- * starters.
97
+ * Produce suggested prompts from both deterministic and cached LLM sources.
98
+ * Deterministic prompts always come first; cached LLM-generated prompts are
99
+ * appended when available. No LLM calls happen in this path — safe for GET.
79
100
  */
80
101
  export async function getSuggestedPrompts(): Promise<SuggestedPrompt[]> {
81
102
  const prompts: SuggestedPrompt[] = [];
82
103
 
104
+ let deterministicPrompts: SuggestedPrompt[] = [];
83
105
  try {
84
- const deterministicPrompts = await getDeterministicPrompts();
106
+ deterministicPrompts = await getDeterministicPrompts();
85
107
  prompts.push(...deterministicPrompts);
86
108
  } catch (err) {
87
109
  log.warn({ err }, "Failed to compute deterministic suggested prompts");
88
110
  }
89
111
 
90
- // Placeholder: assistant-generated prompts will be added here once
91
- // the LLM producer is implemented.
112
+ if (Date.now() - cachedLLMPromptsAt < LLM_CACHE_TTL_MS) {
113
+ prompts.push(...cachedLLMPrompts);
114
+ }
92
115
 
93
116
  return prompts;
94
117
  }
95
118
 
119
+ /**
120
+ * Drops the in-memory LLM suggestion cache so the next call to
121
+ * `getSuggestedPrompts()` returns only the fresh deterministic list (and
122
+ * a follow-up background refresh repopulates the LLM half).
123
+ *
124
+ * Called from OAuth connect/disconnect paths so a freshly-connected
125
+ * provider stops surfacing as a "Connect X" pill within one reload — the
126
+ * 30-minute TTL would otherwise pin a stale suggestion until the next
127
+ * periodic refresh.
128
+ */
129
+ export function invalidateAssistantSuggestedPromptsCache(): void {
130
+ cachedLLMPrompts = [];
131
+ cachedLLMPromptsAt = 0;
132
+ assistantEventHub
133
+ .publish(
134
+ buildAssistantEvent({
135
+ type: "home_feed_updated",
136
+ updatedAt: new Date().toISOString(),
137
+ newItemCount: 0,
138
+ }),
139
+ )
140
+ .catch((err) => {
141
+ log.warn(
142
+ { err },
143
+ "Failed to publish home_feed_updated after prompt cache invalidation",
144
+ );
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Generate LLM-based suggestion prompts and write them to the in-memory
150
+ * cache. No-ops when the cache is still fresh. Intended for background
151
+ * invocation (daemon startup / periodic refresh), not the GET path.
152
+ */
153
+ export async function refreshAssistantSuggestedPrompts(): Promise<void> {
154
+ if (Date.now() - cachedLLMPromptsAt < LLM_CACHE_TTL_MS) {
155
+ return;
156
+ }
157
+
158
+ try {
159
+ const deterministicPrompts = await getDeterministicPrompts();
160
+ const llmPrompts = await generateAssistantPrompts(deterministicPrompts);
161
+ cachedLLMPrompts = llmPrompts;
162
+ cachedLLMPromptsAt = Date.now();
163
+ } catch (err) {
164
+ log.warn({ err }, "Failed to refresh assistant suggested prompts");
165
+ }
166
+ }
167
+
96
168
  /**
97
169
  * Check which well-known OAuth providers are not connected and return
98
170
  * a "Connect X" prompt for each. For connected providers that have
@@ -107,7 +179,7 @@ async function getDeterministicPrompts(): Promise<SuggestedPrompt[]> {
107
179
  const meta = CONNECT_PROMPT_META[provider.provider];
108
180
  if (!meta) continue;
109
181
 
110
- const connected = await isProviderConnected(provider.provider);
182
+ const connected = await isOAuthProviderConnected(provider.provider);
111
183
 
112
184
  if (!connected) {
113
185
  prompts.push({
@@ -135,3 +207,90 @@ async function getDeterministicPrompts(): Promise<SuggestedPrompt[]> {
135
207
 
136
208
  return prompts;
137
209
  }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // LLM-generated suggestions
213
+ // ---------------------------------------------------------------------------
214
+
215
+ interface LLMSuggestion {
216
+ label: string;
217
+ prompt: string;
218
+ }
219
+
220
+ /**
221
+ * Ask the LLM to generate contextual conversation-starter suggestions
222
+ * based on the assistant's persona and the user's connected services.
223
+ * Returns an empty array on failure so deterministic prompts still show.
224
+ */
225
+ async function generateAssistantPrompts(
226
+ deterministicPrompts: SuggestedPrompt[],
227
+ ): Promise<SuggestedPrompt[]> {
228
+ const config = getConfig();
229
+ const resolved = resolveCallSiteConfig("homeSuggestedPrompts", config.llm);
230
+
231
+ const provider = await getConfiguredProvider("homeSuggestedPrompts");
232
+ if (!provider) {
233
+ return [];
234
+ }
235
+
236
+ const systemPrompt = buildSystemPrompt({
237
+ excludeBootstrap: true,
238
+ excludeCustomPrefix: true,
239
+ });
240
+
241
+ const existingLabels = deterministicPrompts.map((p) => p.label).join(", ");
242
+ const contextNote = existingLabels
243
+ ? `The user already has these suggestions: ${existingLabels}. Do NOT duplicate them.`
244
+ : "";
245
+
246
+ const result = await runBtwSidechain({
247
+ content:
248
+ "Suggest 2-3 short, actionable conversation starters for the home page. " +
249
+ "Each should be something specific and helpful you can do for the user right now. " +
250
+ `${contextNote} ` +
251
+ 'Return ONLY a JSON array of objects with "label" (max 5 words) and "prompt" (the full message to send). ' +
252
+ "No markdown fences, no explanation.",
253
+ provider,
254
+ systemPrompt,
255
+ messages: [],
256
+ tools: [],
257
+ callSite: "homeSuggestedPrompts",
258
+ maxTokens: resolved.maxTokens,
259
+ timeoutMs: LLM_SUGGESTIONS_TIMEOUT_MS,
260
+ });
261
+
262
+ const text = result.text.trim();
263
+ if (!text) {
264
+ return [];
265
+ }
266
+
267
+ const parsed = parseLLMSuggestions(text);
268
+ return parsed.map((s, i) => ({
269
+ id: `assistant-${i}-${s.label.toLowerCase().replace(/\s+/g, "-")}`,
270
+ label: s.label,
271
+ prompt: s.prompt,
272
+ source: "assistant" as const,
273
+ }));
274
+ }
275
+
276
+ function parseLLMSuggestions(text: string): LLMSuggestion[] {
277
+ try {
278
+ const cleaned = text
279
+ .replace(/^```(?:json)?\n?/m, "")
280
+ .replace(/\n?```$/m, "");
281
+ const parsed = JSON.parse(cleaned);
282
+ if (!Array.isArray(parsed)) {
283
+ return [];
284
+ }
285
+ return parsed.filter(
286
+ (item): item is LLMSuggestion =>
287
+ typeof item === "object" &&
288
+ item !== null &&
289
+ typeof item.label === "string" &&
290
+ typeof item.prompt === "string",
291
+ );
292
+ } catch {
293
+ log.warn("Failed to parse LLM suggestions response");
294
+ return [];
295
+ }
296
+ }
@@ -0,0 +1,123 @@
1
+ import { connect, type Socket } from "node:net";
2
+
3
+ import { refreshOverridesFromGateway } from "../config/assistant-feature-flags.js";
4
+ import { SYNC_TAGS } from "../daemon/message-types/sync.js";
5
+ import { publishSyncInvalidation } from "../runtime/sync/sync-publisher.js";
6
+ import { getLogger } from "../util/logger.js";
7
+ import { resolveIpcSocketPath } from "./socket-path.js";
8
+
9
+ const log = getLogger("gateway-flag-listener");
10
+
11
+ const MAX_BACKOFF_MS = 30_000;
12
+ const INITIAL_BACKOFF_MS = 1_000;
13
+
14
+ let socket: Socket | null = null;
15
+ let stopped = false;
16
+ let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
17
+ let currentBackoffMs = INITIAL_BACKOFF_MS;
18
+
19
+ function getSocketPath(): string {
20
+ return resolveIpcSocketPath("gateway").path;
21
+ }
22
+
23
+ function handleData(chunk: Buffer): void {
24
+ const lines = chunk.toString().split("\n");
25
+ for (const line of lines) {
26
+ const trimmed = line.trim();
27
+ if (!trimmed) continue;
28
+ try {
29
+ const msg = JSON.parse(trimmed) as { event?: string };
30
+ if (msg.event === "feature_flags_changed") {
31
+ log.info("Received feature_flags_changed event — refreshing overrides");
32
+ refreshOverridesFromGateway().catch((err) => {
33
+ log.warn({ err }, "Failed to refresh feature flag overrides");
34
+ });
35
+ // Fan out to every connected web client so React Query caches
36
+ // for `/v1/feature-flags/client-flag-values/` and
37
+ // `/v1/assistants/:id/feature-flags` invalidate immediately
38
+ // instead of waiting on a 5s polling tick.
39
+ publishSyncInvalidation([
40
+ SYNC_TAGS.featureFlagsClient,
41
+ SYNC_TAGS.featureFlagsAssistant,
42
+ ]).catch((err) => {
43
+ log.warn({ err }, "Failed to broadcast feature-flags sync_changed");
44
+ });
45
+ }
46
+ } catch {
47
+ // Ignore non-JSON lines (e.g. IPC responses on a shared socket)
48
+ }
49
+ }
50
+ }
51
+
52
+ function scheduleReconnect(): void {
53
+ if (stopped) return;
54
+ reconnectTimer = setTimeout(() => {
55
+ reconnectTimer = null;
56
+ connectToGateway();
57
+ }, currentBackoffMs);
58
+ currentBackoffMs = Math.min(currentBackoffMs * 2, MAX_BACKOFF_MS);
59
+ }
60
+
61
+ function connectToGateway(): void {
62
+ if (stopped) return;
63
+
64
+ const socketPath = getSocketPath();
65
+ const conn = connect(socketPath);
66
+
67
+ conn.on("connect", () => {
68
+ if (stopped) {
69
+ conn.destroy();
70
+ return;
71
+ }
72
+ log.info("Connected to gateway IPC for flag events");
73
+ currentBackoffMs = INITIAL_BACKOFF_MS;
74
+ socket = conn;
75
+ refreshOverridesFromGateway().catch((err) => {
76
+ log.warn({ err }, "Failed to refresh feature flag overrides on reconnect");
77
+ });
78
+ });
79
+
80
+ let buffer = "";
81
+ conn.on("data", (chunk) => {
82
+ buffer += chunk.toString();
83
+ let newlineIdx: number;
84
+ while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
85
+ const line = buffer.slice(0, newlineIdx);
86
+ buffer = buffer.slice(newlineIdx + 1);
87
+ if (line.trim()) {
88
+ handleData(Buffer.from(line));
89
+ }
90
+ }
91
+ });
92
+
93
+ conn.on("close", () => {
94
+ socket = null;
95
+ if (!stopped) {
96
+ log.debug("Gateway IPC connection closed — reconnecting");
97
+ scheduleReconnect();
98
+ }
99
+ });
100
+
101
+ conn.on("error", (err) => {
102
+ log.debug({ err }, "Gateway IPC connection error");
103
+ conn.destroy();
104
+ });
105
+ }
106
+
107
+ export function startGatewayFlagListener(): void {
108
+ stopped = false;
109
+ currentBackoffMs = INITIAL_BACKOFF_MS;
110
+ connectToGateway();
111
+ }
112
+
113
+ export function stopGatewayFlagListener(): void {
114
+ stopped = true;
115
+ if (reconnectTimer) {
116
+ clearTimeout(reconnectTimer);
117
+ reconnectTimer = null;
118
+ }
119
+ if (socket) {
120
+ socket.destroy();
121
+ socket = null;
122
+ }
123
+ }
@@ -22,12 +22,9 @@ import { z } from "zod";
22
22
  import type { MeetHostSupervisor } from "../../daemon/meet-host-supervisor.js";
23
23
  import { registerShutdownHook } from "../../daemon/shutdown-registry.js";
24
24
  import { registerSkillRoute } from "../../runtime/skill-route-registry.js";
25
+ import { resolveExecutionTarget } from "../../tools/execution-target.js";
25
26
  import { registerSkillTools } from "../../tools/registry.js";
26
- import type {
27
- ExecutionTarget,
28
- Tool,
29
- ToolDefinition,
30
- } from "../../tools/types.js";
27
+ import type { ExecutionTarget, Tool } from "../../tools/types.js";
31
28
  import { RiskLevel } from "../../tools/types.js";
32
29
  import { getLogger } from "../../util/logger.js";
33
30
  import type { SkillIpcRoute } from "../skill-ipc-types.js";
@@ -182,25 +179,24 @@ export function __getActiveSessionCountForTesting(): number {
182
179
  * exercised end-to-end.
183
180
  */
184
181
  function buildProxyTool(manifest: ToolManifest): Tool {
185
- const definition: ToolDefinition = {
186
- name: manifest.name,
187
- description: manifest.description,
188
- input_schema: manifest.input_schema as object,
189
- };
190
182
  // RiskLevel is a string enum whose values are "low" | "medium" | "high",
191
183
  // matching the schema above exactly — the cast is a no-op at runtime.
192
184
  return {
193
185
  name: manifest.name,
194
186
  description: manifest.description,
187
+ input_schema: manifest.input_schema as object,
195
188
  category: manifest.category,
196
189
  defaultRiskLevel: manifest.defaultRiskLevel as RiskLevel,
197
190
  executionMode: manifest.executionMode ?? "proxy",
198
- executionTarget: manifest.executionTarget as ExecutionTarget | undefined,
191
+ executionTarget: resolveExecutionTarget({
192
+ name: manifest.name,
193
+ executionTarget: manifest.executionTarget as ExecutionTarget | undefined,
194
+ executionMode: manifest.executionMode ?? "proxy",
195
+ }),
199
196
  origin: "skill",
200
197
  ownerSkillId: manifest.ownerSkillId,
201
198
  ownerSkillBundled: manifest.ownerSkillBundled,
202
199
  ownerSkillVersionHash: manifest.ownerSkillVersionHash,
203
- getDefinition: () => definition,
204
200
  execute: async () => {
205
201
  // Only reached when no supervisor is attached (tests/boot race);
206
202
  // the supervisor short-circuit above replaces this with the
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Tests for `db-async-query.ts` — the runAsyncSqlite abstraction.
3
+ *
4
+ * The contract this PR locks in:
5
+ * 1. **The main event loop keeps ticking while a long SQLite
6
+ * statement is in flight via the sqlite3 CLI backend.** This is
7
+ * the structural anti-block assertion — a recursive `setImmediate`
8
+ * probe counts event-loop iterations during the operation; if
9
+ * anyone moves the slow path back onto the main thread,
10
+ * `bun:sqlite` is synchronous and tick counting collapses to ~0;
11
+ * this test fails loudly.
12
+ * 2. The CLI backend reports `backend: "sqlite3-cli"` and `ok: true`
13
+ * on success.
14
+ * 3. The in-process fallback backend executes the statement
15
+ * synchronously and reports `backend: "in-process-blocking"`.
16
+ * 4. Errors from sqlite3 surface as `ok: false` with the stderr
17
+ * preserved in `error`.
18
+ */
19
+ import { beforeEach, describe, expect, test } from "bun:test";
20
+
21
+ const { getSqlite } = await import("../db-connection.js");
22
+ const { initializeDb } = await import("../db-init.js");
23
+ const { runAsyncSqlite, _resetFallbackWarning } =
24
+ await import("../db-async-query.js");
25
+ const { findSqlite3 } = await import("../../util/sqlite3-runtime.js");
26
+
27
+ initializeDb();
28
+
29
+ const sqlite3Available = findSqlite3() !== undefined;
30
+
31
+ beforeEach(() => {
32
+ _resetFallbackWarning();
33
+ });
34
+
35
+ function inflateAndDelete(byteTarget: number): void {
36
+ const sqlite = getSqlite();
37
+ sqlite.exec(
38
+ "CREATE TABLE IF NOT EXISTS async_bloat (id INTEGER PRIMARY KEY, payload BLOB)",
39
+ );
40
+ const pageSize = (
41
+ sqlite.query("PRAGMA page_size").get() as { page_size: number }
42
+ ).page_size;
43
+ const rowsTarget = Math.max(1, Math.ceil(byteTarget / pageSize));
44
+ const payload = new Uint8Array(Math.max(1, pageSize - 64));
45
+ const insert = sqlite.prepare("INSERT INTO async_bloat (payload) VALUES (?)");
46
+ sqlite.exec("BEGIN");
47
+ for (let i = 0; i < rowsTarget; i++) {
48
+ insert.run(payload);
49
+ }
50
+ sqlite.exec("COMMIT");
51
+ sqlite.exec("DELETE FROM async_bloat");
52
+ sqlite.exec("DROP TABLE async_bloat");
53
+ sqlite.exec("PRAGMA wal_checkpoint(TRUNCATE)");
54
+ }
55
+
56
+ describe("runAsyncSqlite", () => {
57
+ test("returns ok=true for a trivial statement", async () => {
58
+ const result = await runAsyncSqlite("SELECT 1");
59
+ expect(result.ok).toBe(true);
60
+ expect(result.error).toBeNull();
61
+ });
62
+
63
+ test("in-process fallback reports the right backend", async () => {
64
+ const result = await runAsyncSqlite("SELECT 1", {
65
+ forceBackend: "in-process-blocking",
66
+ });
67
+ expect(result.ok).toBe(true);
68
+ expect(result.backend).toBe("in-process-blocking");
69
+ });
70
+
71
+ test("in-process fallback emits changes() count on stdout after a DELETE", async () => {
72
+ // Regression for Codex P1 on #31894: callers (e.g. prune jobs) rely
73
+ // on reading the row count off `result.stdout`. The CLI backend
74
+ // populates this naturally when the SQL ends with `SELECT changes();`,
75
+ // but `bun:sqlite`'s `exec()` discards SELECT results — so the
76
+ // in-process backend has to synthesize the same line, or the
77
+ // re-enqueue gate in pruneOld*Job silently never fires on hosts
78
+ // without the sqlite3 CLI.
79
+ const sqlite = getSqlite();
80
+ sqlite.exec(
81
+ "CREATE TABLE IF NOT EXISTS async_changes_probe (id INTEGER PRIMARY KEY)",
82
+ );
83
+ sqlite.exec("DELETE FROM async_changes_probe");
84
+ sqlite.exec(
85
+ "INSERT INTO async_changes_probe (id) VALUES (1),(2),(3),(4),(5)",
86
+ );
87
+
88
+ const result = await runAsyncSqlite(
89
+ "DELETE FROM async_changes_probe WHERE id <= 3; SELECT changes();",
90
+ { forceBackend: "in-process-blocking" },
91
+ );
92
+
93
+ expect(result.ok).toBe(true);
94
+ expect(result.backend).toBe("in-process-blocking");
95
+ // The synthesized stdout matches what the CLI backend would emit:
96
+ // a bare integer on its own line. The exact format keeps the
97
+ // parser in cleanup.ts backend-agnostic.
98
+ expect(result.stdout).toBe("3\n");
99
+
100
+ sqlite.exec("DROP TABLE async_changes_probe");
101
+ });
102
+
103
+ test.if(sqlite3Available)(
104
+ "sqlite3 CLI backend reports the right backend on success",
105
+ async () => {
106
+ const result = await runAsyncSqlite("SELECT 1");
107
+ expect(result.ok).toBe(true);
108
+ expect(result.backend).toBe("sqlite3-cli");
109
+ },
110
+ );
111
+
112
+ test.if(sqlite3Available)(
113
+ "surfaces sqlite3 errors as ok=false with the message preserved",
114
+ async () => {
115
+ // Intentional SQL syntax error.
116
+ const result = await runAsyncSqlite("THIS IS NOT VALID SQL");
117
+ expect(result.ok).toBe(false);
118
+ expect(result.backend).toBe("sqlite3-cli");
119
+ expect(result.error).toBeTruthy();
120
+ },
121
+ );
122
+
123
+ test.if(sqlite3Available)(
124
+ "VACUUM via sqlite3 CLI keeps the event loop ticking (anti-block)",
125
+ async () => {
126
+ // Inflate the DB so VACUUM has measurable work to do. Without
127
+ // this the subprocess finishes in single-digit ms and the
128
+ // probe has no opportunity to record meaningful ticks.
129
+ inflateAndDelete(8 * 1024 * 1024);
130
+
131
+ // Probe the event loop with recursive setImmediate. This fires
132
+ // on every event-loop iteration with no minimum delay, so on a
133
+ // healthy unblocked loop it produces tens of thousands of ticks
134
+ // per second (vs. setInterval(1) which is capped by the host's
135
+ // timer resolution — observed at ~32 ms on GitHub Actions
136
+ // runners). If anyone moves VACUUM back onto the main thread,
137
+ // `bun:sqlite` is sync and tick count collapses to ~0; the
138
+ // assertion below fails loudly. The signal is intentionally
139
+ // binary: "many" vs "none".
140
+ let tickCount = 0;
141
+ let probing = true;
142
+ const tick = (): void => {
143
+ if (!probing) return;
144
+ tickCount += 1;
145
+ setImmediate(tick);
146
+ };
147
+ setImmediate(tick);
148
+
149
+ let result;
150
+ try {
151
+ result = await runAsyncSqlite("VACUUM");
152
+ } finally {
153
+ probing = false;
154
+ }
155
+
156
+ expect(result.ok).toBe(true);
157
+ expect(result.backend).toBe("sqlite3-cli");
158
+
159
+ // Any positive tick count proves the event loop wasn't blocked.
160
+ // A sync in-process VACUUM would collapse this to 0.
161
+ expect(tickCount).toBeGreaterThanOrEqual(1);
162
+ },
163
+ 60_000,
164
+ );
165
+ });
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Tests for `db-maintenance.ts` (orchestration) and the underlying
3
+ * `db-async-query.ts` abstraction.
4
+ *
5
+ * The contract this PR locks in:
6
+ * 1. `runDbMaintenance` runs `VACUUM` through the async abstraction
7
+ * — when the `sqlite3` CLI is available, that means a subprocess
8
+ * and the daemon's main event loop keeps ticking. (The structural
9
+ * anti-block assertion lives in
10
+ * `db-async-query.test.ts`; here we focus on orchestration.)
11
+ * 2. The subprocess actually shrinks the on-disk page count when
12
+ * there's reclaimable space.
13
+ * 3. `maybeRunDbMaintenance` is genuinely async — callers can `await`
14
+ * it and observe completion.
15
+ * 4. The 24 h interval guard short-circuits a recent re-run.
16
+ *
17
+ * The per-file temp workspace is set up by `test-preload.ts`; tests just
18
+ * dynamic-import the DB modules so they resolve paths under that temp dir.
19
+ */
20
+ import { Database } from "bun:sqlite";
21
+ import { beforeEach, describe, expect, test } from "bun:test";
22
+
23
+ const { getSqlite } = await import("../db-connection.js");
24
+ const { initializeDb } = await import("../db-init.js");
25
+ const { deleteMemoryCheckpoint, getMemoryCheckpoint } =
26
+ await import("../checkpoints.js");
27
+ const { maybeRunDbMaintenance } = await import("../db-maintenance.js");
28
+ const { getDbPath } = await import("../../util/platform.js");
29
+
30
+ initializeDb();
31
+
32
+ const MAINTENANCE_CHECKPOINT_KEY = "db_maintenance:last_run";
33
+
34
+ beforeEach(() => {
35
+ deleteMemoryCheckpoint(MAINTENANCE_CHECKPOINT_KEY);
36
+ });
37
+
38
+ /** Inflate the test DB with bloat that VACUUM can reclaim. */
39
+ function inflateAndDelete(byteTarget: number): void {
40
+ const sqlite = getSqlite();
41
+ sqlite.exec(
42
+ "CREATE TABLE IF NOT EXISTS bloat (id INTEGER PRIMARY KEY, payload BLOB)",
43
+ );
44
+ const pageSize = (
45
+ sqlite.query("PRAGMA page_size").get() as { page_size: number }
46
+ ).page_size;
47
+ const rowsTarget = Math.max(1, Math.ceil(byteTarget / pageSize));
48
+ const payload = new Uint8Array(Math.max(1, pageSize - 64));
49
+ const insert = sqlite.prepare("INSERT INTO bloat (payload) VALUES (?)");
50
+ sqlite.exec("BEGIN");
51
+ for (let i = 0; i < rowsTarget; i++) {
52
+ insert.run(payload);
53
+ }
54
+ sqlite.exec("COMMIT");
55
+ sqlite.exec("DELETE FROM bloat");
56
+ sqlite.exec("DROP TABLE bloat");
57
+ // Force the WAL onto the main DB file so the bloat is visible on disk.
58
+ sqlite.exec("PRAGMA wal_checkpoint(TRUNCATE)");
59
+ }
60
+
61
+ describe("maybeRunDbMaintenance", () => {
62
+ test("returns a Promise that resolves", async () => {
63
+ const result = maybeRunDbMaintenance();
64
+ expect(result).toBeInstanceOf(Promise);
65
+ await result;
66
+ });
67
+
68
+ test("respects the 24h interval and skips when last run was recent", async () => {
69
+ const now = Date.now();
70
+ const recent = now - 60_000;
71
+ const { setMemoryCheckpoint } = await import("../checkpoints.js");
72
+ setMemoryCheckpoint(MAINTENANCE_CHECKPOINT_KEY, String(recent));
73
+
74
+ await maybeRunDbMaintenance(now);
75
+
76
+ expect(getMemoryCheckpoint(MAINTENANCE_CHECKPOINT_KEY)).toBe(
77
+ String(recent),
78
+ );
79
+ });
80
+
81
+ test("stamps the checkpoint after a maintenance run", async () => {
82
+ const now = Date.now();
83
+ await maybeRunDbMaintenance(now);
84
+
85
+ expect(getMemoryCheckpoint(MAINTENANCE_CHECKPOINT_KEY)).toBe(String(now));
86
+ });
87
+
88
+ test("VACUUM reclaims pages on a bloated DB", async () => {
89
+ const sqlite = getSqlite();
90
+ sqlite.exec("DROP TABLE IF EXISTS bloat");
91
+ sqlite.exec("PRAGMA wal_checkpoint(TRUNCATE)");
92
+
93
+ inflateAndDelete(8 * 1024 * 1024);
94
+
95
+ const dbPath = getDbPath();
96
+ // Read page_count from a fresh connection so we observe post-write
97
+ // ground truth without snapshot caching on the main test connection.
98
+ const readPageCount = (): number => {
99
+ const probe = new Database(dbPath, { readonly: true });
100
+ try {
101
+ return (
102
+ probe.query("PRAGMA page_count").get() as { page_count: number }
103
+ ).page_count;
104
+ } finally {
105
+ probe.close();
106
+ }
107
+ };
108
+ const pagesBefore = readPageCount();
109
+
110
+ await maybeRunDbMaintenance();
111
+
112
+ const pagesAfter = readPageCount();
113
+ expect(pagesAfter).toBeLessThan(pagesBefore);
114
+ }, 60_000);
115
+ });