@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e

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 (456) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +8 -0
  3. package/docs/activation-funnel-telemetry.md +13 -7
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  12. package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  13. package/openapi.yaml +530 -107
  14. package/package.json +1 -1
  15. package/scripts/generate-openapi.ts +8 -0
  16. package/src/__tests__/activation-early-marking.test.ts +6 -5
  17. package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
  18. package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
  19. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
  20. package/src/__tests__/app-bundler.test.ts +4 -12
  21. package/src/__tests__/app-executors.test.ts +2 -49
  22. package/src/__tests__/app-routes-csp.test.ts +114 -146
  23. package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
  24. package/src/__tests__/build-persisted-content.test.ts +96 -0
  25. package/src/__tests__/bundle-scanner.test.ts +27 -1
  26. package/src/__tests__/call-controller.test.ts +291 -0
  27. package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
  28. package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
  29. package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
  30. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  31. package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +9 -4
  33. package/src/__tests__/config-schema-cmd.test.ts +10 -11
  34. package/src/__tests__/config-schema.test.ts +190 -257
  35. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
  37. package/src/__tests__/conversation-agent-loop.test.ts +22 -9
  38. package/src/__tests__/conversation-error.test.ts +31 -0
  39. package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
  40. package/src/__tests__/conversation-process-callsite.test.ts +12 -19
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
  42. package/src/__tests__/conversation-summarize-route.test.ts +36 -44
  43. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  44. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
  45. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
  46. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
  47. package/src/__tests__/conversation-usage.test.ts +4 -12
  48. package/src/__tests__/credential-routes.test.ts +250 -0
  49. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  50. package/src/__tests__/db-migration-rollback.test.ts +22 -0
  51. package/src/__tests__/empty-response-hook.test.ts +186 -1
  52. package/src/__tests__/external-plugin-loader.test.ts +11 -5
  53. package/src/__tests__/heartbeat-service.test.ts +0 -28
  54. package/src/__tests__/host-shell-tool.test.ts +2 -0
  55. package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
  56. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
  57. package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
  58. package/src/__tests__/invite-routes-http.test.ts +12 -16
  59. package/src/__tests__/list-all-apps.test.ts +0 -4
  60. package/src/__tests__/llm-context-resolution.test.ts +32 -56
  61. package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
  62. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
  63. package/src/__tests__/llm-resolver.test.ts +342 -602
  64. package/src/__tests__/llm-schema.test.ts +79 -37
  65. package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
  66. package/src/__tests__/media-stream-output.test.ts +259 -3
  67. package/src/__tests__/media-stream-server-integration.test.ts +22 -1
  68. package/src/__tests__/media-stream-stt-session.test.ts +47 -0
  69. package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
  70. package/src/__tests__/memory-recall-log-store.test.ts +47 -13
  71. package/src/__tests__/mock-gateway-ipc.ts +46 -1
  72. package/src/__tests__/mtime-cache.test.ts +61 -0
  73. package/src/__tests__/navigate-settings-tab.test.ts +2 -0
  74. package/src/__tests__/normalize-onboarding.test.ts +33 -0
  75. package/src/__tests__/onboarding-persona-write.test.ts +26 -0
  76. package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
  77. package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
  78. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
  79. package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
  80. package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
  81. package/src/__tests__/provider-connections-backfill.test.ts +138 -0
  82. package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
  83. package/src/__tests__/provider-registry-ollama.test.ts +8 -18
  84. package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
  85. package/src/__tests__/provider-usage-tracking.test.ts +9 -19
  86. package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
  87. package/src/__tests__/published-app-updater.test.ts +22 -16
  88. package/src/__tests__/registry.test.ts +5 -16
  89. package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
  90. package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
  91. package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
  92. package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
  93. package/src/__tests__/reveal-success-registry.test.ts +123 -0
  94. package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
  95. package/src/__tests__/secret-fixtures.ts +9 -0
  96. package/src/__tests__/server-history-render.test.ts +28 -0
  97. package/src/__tests__/skills.test.ts +9 -4
  98. package/src/__tests__/slack-share-routes.test.ts +0 -1
  99. package/src/__tests__/stt-stream-session.test.ts +6 -5
  100. package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
  101. package/src/__tests__/subagent-disposal.test.ts +2 -0
  102. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  103. package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
  104. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  105. package/src/__tests__/subagent-role-registry.test.ts +37 -0
  106. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  107. package/src/__tests__/subagent-terminal-message.test.ts +50 -0
  108. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
  109. package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
  110. package/src/__tests__/telemetry-routes.test.ts +99 -19
  111. package/src/__tests__/tool-audit.test.ts +34 -4
  112. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  113. package/src/__tests__/tool-profiler.test.ts +72 -1
  114. package/src/__tests__/tool-result-spool.test.ts +49 -4
  115. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
  116. package/src/__tests__/ui-channel-variants.test.ts +108 -0
  117. package/src/__tests__/ui-shape-teaching.test.ts +255 -0
  118. package/src/__tests__/usage-attribution.test.ts +18 -41
  119. package/src/__tests__/user-plugin-loader.test.ts +4 -4
  120. package/src/__tests__/voice-config-update.test.ts +46 -0
  121. package/src/__tests__/voice-session-bridge.test.ts +216 -84
  122. package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
  123. package/src/agent/loop.ts +4 -3
  124. package/src/api/events/open-conversation.test.ts +64 -0
  125. package/src/api/events/open-conversation.ts +33 -0
  126. package/src/api/index.ts +6 -0
  127. package/src/api/responses/conversation-message.ts +5 -0
  128. package/src/apps/app-store.ts +25 -35
  129. package/src/bundler/app-bundler.ts +34 -48
  130. package/src/bundler/app-compiler.ts +39 -4
  131. package/src/bundler/bundle-scanner.ts +13 -0
  132. package/src/bundler/manifest.ts +1 -1
  133. package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
  134. package/src/calls/call-constants.ts +5 -0
  135. package/src/calls/call-controller.ts +100 -32
  136. package/src/calls/call-transport.ts +9 -0
  137. package/src/calls/media-stream-output.ts +107 -4
  138. package/src/calls/media-stream-server.ts +29 -9
  139. package/src/calls/media-stream-stt-session.ts +7 -1
  140. package/src/calls/media-turn-detector.ts +11 -1
  141. package/src/calls/voice-session-bridge.ts +84 -63
  142. package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
  143. package/src/cli/commands/__tests__/notifications.test.ts +24 -3
  144. package/src/cli/commands/config.help.ts +6 -6
  145. package/src/cli/commands/credentials.help.ts +13 -0
  146. package/src/cli/commands/credentials.ts +6 -1
  147. package/src/cli/commands/email.help.ts +7 -0
  148. package/src/cli/commands/email.ts +35 -1
  149. package/src/cli/commands/inference-providers.ts +168 -107
  150. package/src/cli/commands/inference.help.ts +118 -46
  151. package/src/cli/commands/memory/index.help.ts +23 -0
  152. package/src/cli/commands/memory/nodes.ts +146 -0
  153. package/src/cli/commands/notifications.help.ts +10 -10
  154. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
  155. package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
  156. package/src/cli/commands/oauth/connect.test.ts +126 -0
  157. package/src/cli/commands/oauth/connect.ts +29 -6
  158. package/src/cli/commands/oauth/index.help.ts +7 -1
  159. package/src/cli/commands/oauth/status.test.ts +69 -3
  160. package/src/cli/commands/oauth/status.ts +50 -12
  161. package/src/cli/commands/plugins.help.ts +13 -2
  162. package/src/cli/commands/plugins.ts +61 -7
  163. package/src/cli/commands/telemetry.help.ts +13 -0
  164. package/src/cli/commands/telemetry.ts +45 -5
  165. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
  166. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
  167. package/src/cli/lib/bundled-marketplace.json +93 -36
  168. package/src/cli/lib/inspect-plugin.ts +5 -1
  169. package/src/cli/lib/install-from-github.ts +68 -4
  170. package/src/cli/lib/plugin-catalog-local.ts +6 -2
  171. package/src/cli/lib/plugin-fingerprint.ts +3 -3
  172. package/src/cli/lib/upgrade-plugin.ts +236 -35
  173. package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
  174. package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
  175. package/src/config/__tests__/profile-materialization.test.ts +1 -88
  176. package/src/config/bundled-skills/AGENTS.md +3 -30
  177. package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
  178. package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
  180. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  181. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
  182. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  183. package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
  184. package/src/config/bundled-skills/settings/TOOLS.json +3 -1
  185. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
  186. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
  187. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  188. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  189. package/src/config/call-site-defaults.ts +3 -3
  190. package/src/config/feature-flag-registry.json +24 -39
  191. package/src/config/llm-resolver.ts +88 -475
  192. package/src/config/profile-materialization.ts +11 -11
  193. package/src/config/schema.ts +93 -127
  194. package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
  195. package/src/config/schemas/__tests__/stt.test.ts +31 -3
  196. package/src/config/schemas/live-voice.ts +10 -4
  197. package/src/config/schemas/llm.ts +52 -15
  198. package/src/config/schemas/memory-lifecycle.ts +1 -1
  199. package/src/config/schemas/memory-retrospective.ts +8 -0
  200. package/src/config/schemas/memory-v2.ts +2 -2
  201. package/src/config/schemas/memory-v3.ts +1 -1
  202. package/src/config/schemas/services.ts +6 -3
  203. package/src/config/schemas/stt.ts +38 -21
  204. package/src/config/schemas/tts.ts +13 -18
  205. package/src/config/skills.ts +31 -21
  206. package/src/context/compactor.ts +44 -7
  207. package/src/context/post-turn-tool-result-truncation.ts +4 -2
  208. package/src/context/tool-result-spool.ts +26 -5
  209. package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
  210. package/src/conversations/message-consolidation.ts +22 -2
  211. package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
  212. package/src/daemon/app-source-watcher.ts +17 -23
  213. package/src/daemon/chat-credential-redaction.ts +1365 -0
  214. package/src/daemon/conversation-agent-loop-handlers.ts +627 -34
  215. package/src/daemon/conversation-agent-loop.ts +71 -31
  216. package/src/daemon/conversation-error.ts +36 -14
  217. package/src/daemon/conversation-process.ts +22 -0
  218. package/src/daemon/conversation-store.ts +35 -0
  219. package/src/daemon/conversation-surfaces.ts +25 -25
  220. package/src/daemon/conversation-tool-setup.ts +33 -7
  221. package/src/daemon/conversation.ts +55 -5
  222. package/src/daemon/handlers/shared.ts +33 -2
  223. package/src/daemon/lifecycle.ts +4 -4
  224. package/src/daemon/message-types/conversations.ts +6 -17
  225. package/src/daemon/providers-setup.ts +8 -0
  226. package/src/daemon/tool-setup-types.ts +7 -1
  227. package/src/daemon/wake-conversation-ops.ts +10 -1
  228. package/src/hooks/types.ts +9 -0
  229. package/src/ipc/__tests__/email-ipc.test.ts +90 -0
  230. package/src/ipc/gateway-client.test.ts +59 -0
  231. package/src/ipc/gateway-client.ts +70 -27
  232. package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
  233. package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
  234. package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
  235. package/src/live-voice/__tests__/protocol.test.ts +122 -0
  236. package/src/live-voice/live-voice-session.ts +443 -40
  237. package/src/live-voice/protocol.ts +143 -1
  238. package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
  239. package/src/monitoring/plugin-source-watch.ts +3 -62
  240. package/src/notifications/README.md +1 -1
  241. package/src/permissions/checker.ts +8 -4
  242. package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
  243. package/src/persistence/conversation-crud.ts +104 -2
  244. package/src/persistence/db-init.ts +14 -3
  245. package/src/persistence/job-handlers/cleanup.ts +15 -7
  246. package/src/persistence/llm-request-log-store.ts +88 -52
  247. package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
  248. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
  249. package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
  250. package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
  251. package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
  252. package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
  253. package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
  254. package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
  255. package/src/persistence/migrations/helpers/relocation.ts +44 -1
  256. package/src/persistence/migrations/run-migrations.ts +25 -1
  257. package/src/persistence/schema/infrastructure.ts +9 -0
  258. package/src/persistence/schema/memory-core.ts +3 -0
  259. package/src/persistence/schema/memory-injection.ts +2 -0
  260. package/src/persistence/steps.ts +36 -0
  261. package/src/platform/client.test.ts +1 -44
  262. package/src/platform/client.ts +10 -20
  263. package/src/platform/consent-cache.test.ts +89 -23
  264. package/src/platform/consent-cache.ts +71 -33
  265. package/src/plugin-api/constants.ts +12 -0
  266. package/src/plugin-api/conversation-turn.ts +37 -14
  267. package/src/plugin-api/index.ts +11 -1
  268. package/src/plugin-api/resolve-credential.ts +75 -0
  269. package/src/plugin-api/vision-support.test.ts +8 -19
  270. package/src/plugin-api/vision-support.ts +26 -17
  271. package/src/plugins/collect-source-versions.ts +77 -0
  272. package/src/plugins/defaults/compaction/compact.ts +6 -0
  273. package/src/plugins/defaults/compaction/window-manager.ts +9 -0
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
  275. package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
  276. package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
  277. package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
  278. package/src/plugins/defaults/index.ts +8 -1
  279. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
  280. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
  281. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
  282. package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
  283. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
  284. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
  285. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
  286. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
  287. package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
  288. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
  289. package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
  290. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
  291. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
  292. package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
  293. package/src/plugins/defaults/memory/graph/store.ts +114 -0
  294. package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
  295. package/src/plugins/defaults/memory/graph/tools.ts +47 -13
  296. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
  297. package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
  298. package/src/plugins/defaults/memory/memory-db.ts +3 -2
  299. package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
  300. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
  301. package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
  302. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
  303. package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
  304. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
  305. package/src/plugins/defaults/memory/path-containment.ts +21 -0
  306. package/src/plugins/defaults/memory/prompt-override.ts +51 -13
  307. package/src/plugins/defaults/memory/tools.test.ts +34 -0
  308. package/src/plugins/defaults/memory/tools.ts +12 -2
  309. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
  310. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
  311. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
  312. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
  313. package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
  314. package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
  315. package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
  316. package/src/plugins/defaults/memory/v2/migration.ts +46 -16
  317. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
  318. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
  319. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
  320. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
  321. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
  322. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
  323. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
  324. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
  325. package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
  326. package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
  327. package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
  328. package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
  329. package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
  330. package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
  331. package/src/plugins/defaults/memory/v3/prune.ts +17 -11
  332. package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
  333. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
  334. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
  335. package/src/plugins/external-plugin-loader.ts +15 -15
  336. package/src/plugins/mtime-cache.ts +47 -0
  337. package/src/plugins/pipeline.ts +9 -1
  338. package/src/plugins/plugin-execution-context.ts +44 -0
  339. package/src/plugins/plugin-tree-walk.ts +31 -24
  340. package/src/plugins/source-fingerprint.ts +3 -4
  341. package/src/prompts/normalize-onboarding.ts +12 -0
  342. package/src/prompts/persona-resolver.ts +8 -0
  343. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
  344. package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
  345. package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
  346. package/src/providers/__tests__/retry-callsite.test.ts +235 -215
  347. package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
  348. package/src/providers/atlascloud/client.ts +10 -49
  349. package/src/providers/baseten/client.ts +43 -0
  350. package/src/providers/call-site-routing.ts +26 -13
  351. package/src/providers/connection-resolution.ts +9 -11
  352. package/src/providers/fetch-provider-catalog.ts +4 -2
  353. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
  354. package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
  355. package/src/providers/inference/adapter-factory.ts +27 -6
  356. package/src/providers/inference/auth.ts +29 -1
  357. package/src/providers/inference/backfill.ts +61 -10
  358. package/src/providers/inference/connection-availability.ts +3 -2
  359. package/src/providers/inference/resolve-auth.ts +9 -1
  360. package/src/providers/model-catalog.ts +37 -0
  361. package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
  362. package/src/providers/openai/api-key-validation.ts +70 -0
  363. package/src/providers/retry.ts +2 -0
  364. package/src/providers/types.ts +11 -4
  365. package/src/providers/vellum-model-routing.test.ts +26 -0
  366. package/src/providers/vellum-model-routing.ts +30 -0
  367. package/src/providers/voice-error-copy.ts +47 -0
  368. package/src/runtime/agent-wake.ts +40 -24
  369. package/src/runtime/for-chat-mint-registry.ts +118 -0
  370. package/src/runtime/reveal-nonce.ts +49 -0
  371. package/src/runtime/reveal-success-registry.ts +306 -0
  372. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
  373. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
  374. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
  375. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
  376. package/src/runtime/routes/app-management-routes.ts +152 -45
  377. package/src/runtime/routes/app-routes.ts +16 -77
  378. package/src/runtime/routes/canned-message-complete.ts +16 -16
  379. package/src/runtime/routes/conversation-management-routes.ts +17 -14
  380. package/src/runtime/routes/conversation-query-routes.ts +54 -6
  381. package/src/runtime/routes/conversation-routes.ts +48 -2
  382. package/src/runtime/routes/credential-routes.ts +116 -3
  383. package/src/runtime/routes/email-routes.ts +17 -1
  384. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
  385. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
  386. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
  387. package/src/runtime/routes/inference-send-routes.ts +1 -1
  388. package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
  389. package/src/runtime/routes/plugins-routes.ts +37 -6
  390. package/src/runtime/routes/publish-routes.ts +15 -18
  391. package/src/runtime/routes/schedule-worker-routes.ts +9 -0
  392. package/src/runtime/routes/secret-routes.ts +10 -0
  393. package/src/runtime/routes/telemetry-routes.ts +149 -45
  394. package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
  395. package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
  396. package/src/schedule/schedule-store.ts +12 -1
  397. package/src/schedule/schedule-timezone.ts +63 -0
  398. package/src/schedule/scheduler.ts +100 -6
  399. package/src/schedule/worker-control.ts +19 -0
  400. package/src/security/auth-fallback-events-store.ts +7 -6
  401. package/src/security/secret-scanner.ts +26 -1
  402. package/src/services/published-app-updater.ts +6 -11
  403. package/src/stt/stt-stream-session.ts +36 -16
  404. package/src/subagent/manager.ts +34 -8
  405. package/src/telemetry/AGENTS.md +30 -1
  406. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
  407. package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
  408. package/src/telemetry/config-setting-snapshot.ts +44 -10
  409. package/src/telemetry/telemetry-event-sources.test.ts +124 -30
  410. package/src/telemetry/telemetry-event-sources.ts +137 -83
  411. package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
  412. package/src/telemetry/telemetry-events-outbox.ts +60 -9
  413. package/src/telemetry/telemetry-wire-source.json +1 -1
  414. package/src/telemetry/telemetry-wire-validation.ts +39 -2
  415. package/src/telemetry/telemetry-wire.generated.ts +8 -0
  416. package/src/telemetry/tool-audit.ts +15 -9
  417. package/src/telemetry/tool-executed-events-store.test.ts +1 -1
  418. package/src/telemetry/turn-events-store.ts +20 -0
  419. package/src/telemetry/types.ts +45 -12
  420. package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
  421. package/src/telemetry/usage-telemetry-reporter.ts +117 -18
  422. package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
  423. package/src/telemetry/watchdog-direct-emit.ts +11 -6
  424. package/src/tools/apps/executors.ts +11 -36
  425. package/src/tools/executor.ts +14 -1
  426. package/src/tools/host-terminal/host-shell.ts +6 -2
  427. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
  428. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
  429. package/src/tools/network/web-fetch.ts +6 -2
  430. package/src/tools/skills/sandbox-runner.ts +5 -2
  431. package/src/tools/subagent/spawn.ts +7 -11
  432. package/src/tools/terminal/shell.ts +5 -2
  433. package/src/tools/tool-manifest.ts +0 -2
  434. package/src/tools/tool-profiler.ts +37 -6
  435. package/src/tools/ui-surface/channel-variants.ts +101 -0
  436. package/src/tools/ui-surface/definitions.ts +23 -67
  437. package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
  438. package/src/tts/__tests__/provider-adapters.test.ts +18 -0
  439. package/src/tts/provider-catalog.ts +2 -4
  440. package/src/tts/providers/deepgram-provider.ts +10 -1
  441. package/src/types/onboarding-context.ts +8 -0
  442. package/src/usage/attribution.ts +18 -112
  443. package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
  444. package/src/util/provider-error-patterns.ts +7 -1
  445. package/src/util/worker-process.ts +105 -0
  446. package/src/watcher/__tests__/telemetry.test.ts +17 -4
  447. package/src/watcher/telemetry.ts +5 -5
  448. package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
  449. package/src/workspace/migrations/registry.ts +2 -0
  450. package/src/workspace/provider-commit-message-generator.ts +6 -4
  451. package/src/__tests__/app-open-proxy.test.ts +0 -67
  452. package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
  453. package/src/onboarding/onboarding-research-events-store.ts +0 -104
  454. package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
  455. package/src/tools/apps/definitions.ts +0 -73
  456. package/src/tools/apps/open-proxy.ts +0 -43
@@ -1,18 +1,23 @@
1
1
  /**
2
2
  * Tests for the frecency hot-set lane (`hot-set.ts`) and its config schema.
3
3
  *
4
- * `computeHotSet` takes the db handle directly (no module mocks needed):
5
- * each test seeds an in-memory SQLite db with `memory_v3_selections` rows
6
- * (via the real migration) and asserts the decayed-frequency ranking.
4
+ * `computeHotSet` reads `memory_v3_selections` over the dedicated memory
5
+ * connection: each test installs an in-memory SQLite db into the `memory`
6
+ * singleton slot (where the accessor resolves its connection), seeds it with
7
+ * selection rows, and asserts the decayed-frequency ranking.
7
8
  */
8
9
 
9
10
  import { Database } from "bun:sqlite";
10
- import { beforeEach, describe, expect, test } from "bun:test";
11
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
12
 
12
13
  import { drizzle } from "drizzle-orm/bun-sqlite";
13
14
 
14
15
  import { MemoryV3ConfigSchema } from "../../../../config/schemas/memory-v3.js";
15
- import { migrateAddMemoryV3Selections } from "../../../../persistence/migrations/268-add-memory-v3-selections.js";
16
+ import {
17
+ clearStoredDb,
18
+ setStoredDb,
19
+ } from "../../../../persistence/db-singleton.js";
20
+ import { ensureMemoryV3SelectionsSchema } from "../../../../persistence/migrations/338-move-memory-v3-selections-to-memory-db.js";
16
21
  import * as schema from "../../../../persistence/schema/index.js";
17
22
  import { computeHotSet, type HotSetOptions } from "./hot-set.js";
18
23
 
@@ -20,12 +25,15 @@ const HALF_LIFE_MS = 1000;
20
25
  const NOW = 100_000;
21
26
 
22
27
  let sqlite: Database;
23
- let db: ReturnType<typeof drizzle<typeof schema>>;
24
28
 
25
29
  beforeEach(() => {
26
30
  sqlite = new Database(":memory:");
27
- db = drizzle(sqlite, { schema });
28
- migrateAddMemoryV3Selections(db);
31
+ ensureMemoryV3SelectionsSchema(sqlite);
32
+ setStoredDb("memory", drizzle(sqlite, { schema }), () => sqlite.close());
33
+ });
34
+
35
+ afterEach(() => {
36
+ clearStoredDb("memory");
29
37
  });
30
38
 
31
39
  let nextTurn = 0;
@@ -40,16 +48,13 @@ function seed(slug: string, createdAts: number[]): void {
40
48
  }
41
49
 
42
50
  function hotSet(overrides: Partial<HotSetOptions> = {}) {
43
- return computeHotSet(
44
- { db },
45
- {
46
- k: 10,
47
- halfLifeMs: HALF_LIFE_MS,
48
- now: NOW,
49
- excludeSlugs: new Set<string>(),
50
- ...overrides,
51
- },
52
- );
51
+ return computeHotSet({
52
+ k: 10,
53
+ halfLifeMs: HALF_LIFE_MS,
54
+ now: NOW,
55
+ excludeSlugs: new Set<string>(),
56
+ ...overrides,
57
+ });
53
58
  }
54
59
 
55
60
  describe("computeHotSet", () => {
@@ -111,6 +116,19 @@ describe("computeHotSet", () => {
111
116
  seed("page-a", [NOW + 50 * HALF_LIFE_MS]);
112
117
  expect(hotSet()[0]!.score).toBeCloseTo(1, 10);
113
118
  });
119
+
120
+ test("degrades to an empty hot set when the memory database is unavailable", () => {
121
+ seed("page-a", [NOW]);
122
+ // Simulate unavailability through the singleton slot instead of
123
+ // `mock.module`, which is process-global and leaks into sibling test
124
+ // files. The accessor extracts the raw connection from the stored
125
+ // handle's `$client`, so a handle without one makes `getMemorySqlite()`
126
+ // resolve to null, the same contract computeHotSet sees when the
127
+ // dedicated open fails.
128
+ clearStoredDb("memory");
129
+ setStoredDb("memory", { $client: null }, () => {});
130
+ expect(hotSet()).toEqual([]);
131
+ });
114
132
  });
115
133
 
116
134
  describe("MemoryV3ConfigSchema hotSet", () => {
@@ -14,17 +14,9 @@
14
14
  * the hot lane never duplicates core.
15
15
  */
16
16
 
17
- import {
18
- type DrizzleDb,
19
- getSqliteFrom,
20
- } from "../../../../persistence/db-connection.js";
17
+ import { memorySqliteOrNull } from "../memory-db.js";
21
18
  import type { Slug } from "./types.js";
22
19
 
23
- export interface HotSetDeps {
24
- /** Handle to the database containing `memory_v3_selections`. */
25
- db: DrizzleDb;
26
- }
27
-
28
20
  export interface HotSetOptions {
29
21
  /** Maximum number of slugs returned. */
30
22
  k: number;
@@ -52,15 +44,21 @@ interface SelectionAgeRow {
52
44
  * Deterministic for fixed inputs: ties break score desc, then slug asc. The
53
45
  * decay sum runs in TS over `(slug, created_at)` pairs — the selections table
54
46
  * is small enough that reading it directly beats pushing the math into SQL.
47
+ *
48
+ * Reads `memory_v3_selections` over the dedicated memory connection.
49
+ * Fail-soft: an unavailable memory database yields an empty hot set — the
50
+ * lane simply contributes nothing to the stable prefix.
55
51
  */
56
- export function computeHotSet(
57
- deps: HotSetDeps,
58
- opts: HotSetOptions,
59
- ): HotSetEntry[] {
52
+ export function computeHotSet(opts: HotSetOptions): HotSetEntry[] {
60
53
  const { k, halfLifeMs, now, excludeSlugs } = opts;
61
54
  if (k <= 0) return [];
62
55
 
63
- const rows = getSqliteFrom(deps.db)
56
+ const raw = memorySqliteOrNull("computeHotSet");
57
+ if (!raw) {
58
+ return [];
59
+ }
60
+
61
+ const rows = raw
64
62
  .query(
65
63
  /*sql*/ `
66
64
  SELECT slug, created_at FROM memory_v3_selections
@@ -1,18 +1,23 @@
1
1
  /**
2
2
  * Tests for the learned-edge lane (`learned-edges.ts`).
3
3
  *
4
- * `computeLearnedEdgeGraph` takes the db handle directly (no module mocks):
5
- * each test seeds an in-memory SQLite db with `memory_v3_selections` rows
6
- * (via the real migration) and asserts the NPMI association graph. Rows
4
+ * `computeLearnedEdgeGraph` reads `memory_v3_selections` over the dedicated
5
+ * memory connection: each test installs an in-memory SQLite db into the
6
+ * `memory` singleton slot (where the accessor resolves its connection), seeds
7
+ * it with selection rows, and asserts the NPMI association graph. Rows
7
8
  * sharing a `(conversation_id, created_at)` form one selector call.
8
9
  */
9
10
 
10
11
  import { Database } from "bun:sqlite";
11
- import { beforeEach, describe, expect, test } from "bun:test";
12
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
13
 
13
14
  import { drizzle } from "drizzle-orm/bun-sqlite";
14
15
 
15
- import { migrateAddMemoryV3Selections } from "../../../../persistence/migrations/268-add-memory-v3-selections.js";
16
+ import {
17
+ clearStoredDb,
18
+ setStoredDb,
19
+ } from "../../../../persistence/db-singleton.js";
20
+ import { ensureMemoryV3SelectionsSchema } from "../../../../persistence/migrations/338-move-memory-v3-selections-to-memory-db.js";
16
21
  import * as schema from "../../../../persistence/schema/index.js";
17
22
  import {
18
23
  computeLearnedEdgeGraph,
@@ -23,12 +28,15 @@ const HALF_LIFE_MS = 1_000_000;
23
28
  const NOW = 100_000;
24
29
 
25
30
  let sqlite: Database;
26
- let db: ReturnType<typeof drizzle<typeof schema>>;
27
31
 
28
32
  beforeEach(() => {
29
33
  sqlite = new Database(":memory:");
30
- db = drizzle(sqlite, { schema });
31
- migrateAddMemoryV3Selections(db);
34
+ ensureMemoryV3SelectionsSchema(sqlite);
35
+ setStoredDb("memory", drizzle(sqlite, { schema }), () => sqlite.close());
36
+ });
37
+
38
+ afterEach(() => {
39
+ clearStoredDb("memory");
32
40
  });
33
41
 
34
42
  let nextTurn = 0;
@@ -44,25 +52,22 @@ function seedCall(slugs: string[], createdAt = NOW, conv = "conv-1"): void {
44
52
  }
45
53
 
46
54
  function graphOf(overrides: Partial<LearnedEdgesOptions> = {}) {
47
- return computeLearnedEdgeGraph(
48
- { db },
49
- {
50
- halfLifeMs: HALF_LIFE_MS,
51
- minCount: 2,
52
- npmiFloor: 0.2,
53
- maxPerPage: 6,
54
- now: NOW,
55
- windowMs: NOW,
56
- knownSlugs: new Set([
57
- "page-a",
58
- "page-b",
59
- "page-c",
60
- "page-d",
61
- "skills/widget",
62
- ]),
63
- ...overrides,
64
- },
65
- );
55
+ return computeLearnedEdgeGraph({
56
+ halfLifeMs: HALF_LIFE_MS,
57
+ minCount: 2,
58
+ npmiFloor: 0.2,
59
+ maxPerPage: 6,
60
+ now: NOW,
61
+ windowMs: NOW,
62
+ knownSlugs: new Set([
63
+ "page-a",
64
+ "page-b",
65
+ "page-c",
66
+ "page-d",
67
+ "skills/widget",
68
+ ]),
69
+ ...overrides,
70
+ });
66
71
  }
67
72
 
68
73
  const peersOf = (
@@ -180,4 +185,19 @@ describe("computeLearnedEdgeGraph", () => {
180
185
  const graph = graphOf({ windowMs: 10_000 });
181
186
  expect(graph.adjacency.size).toBe(0);
182
187
  });
188
+
189
+ test("degrades to an empty graph when the memory database is unavailable", () => {
190
+ // Simulate unavailability through the singleton slot instead of
191
+ // `mock.module`, which is process-global and leaks into sibling test
192
+ // files. The accessor extracts the raw connection from the stored
193
+ // handle's `$client`, so a handle without one makes `getMemorySqlite()`
194
+ // resolve to null, the same contract computeLearnedEdgeGraph sees when
195
+ // the dedicated open fails.
196
+ clearStoredDb("memory");
197
+ setStoredDb("memory", { $client: null }, () => {});
198
+
199
+ const graph = graphOf();
200
+ expect(graph.adjacency.size).toBe(0);
201
+ expect(graph.hubs.size).toBe(0);
202
+ });
183
203
  });
@@ -38,18 +38,10 @@
38
38
  * the persistence; nothing else is stored.
39
39
  */
40
40
 
41
- import {
42
- type DrizzleDb,
43
- getSqliteFrom,
44
- } from "../../../../persistence/db-connection.js";
41
+ import { memorySqliteOrNull } from "../memory-db.js";
45
42
  import type { EdgeGraph } from "./edge.js";
46
43
  import type { Slug } from "./types.js";
47
44
 
48
- export interface LearnedEdgesDeps {
49
- /** Handle to the database containing `memory_v3_selections`. */
50
- db: DrizzleDb;
51
- }
52
-
53
45
  export interface LearnedEdgesOptions {
54
46
  /** Decay half-life in milliseconds: a selector call this old contributes
55
47
  * half the weight of one made now. */
@@ -86,11 +78,12 @@ interface SelectionRow {
86
78
  * Deterministic for fixed inputs: per-page neighbors order by NPMI desc, then
87
79
  * peer slug asc. Symmetric by construction — an edge contributes to BOTH
88
80
  * endpoints' neighbor lists (each independently subject to `maxPerPage`).
81
+ *
82
+ * Reads `memory_v3_selections` over the dedicated memory connection.
83
+ * Fail-soft: an unavailable memory database yields an empty graph — the lane
84
+ * simply surfaces no learned associations.
89
85
  */
90
- export function computeLearnedEdgeGraph(
91
- deps: LearnedEdgesDeps,
92
- opts: LearnedEdgesOptions,
93
- ): EdgeGraph {
86
+ export function computeLearnedEdgeGraph(opts: LearnedEdgesOptions): EdgeGraph {
94
87
  const { halfLifeMs, minCount, npmiFloor, maxPerPage, now, windowMs } = opts;
95
88
  const empty: EdgeGraph = {
96
89
  adjacency: new Map(),
@@ -99,7 +92,12 @@ export function computeLearnedEdgeGraph(
99
92
  };
100
93
  if (maxPerPage <= 0) return empty;
101
94
 
102
- const rows = getSqliteFrom(deps.db)
95
+ const raw = memorySqliteOrNull("computeLearnedEdgeGraph");
96
+ if (!raw) {
97
+ return empty;
98
+ }
99
+
100
+ const rows = raw
103
101
  .query(
104
102
  /*sql*/ `
105
103
  SELECT conversation_id, slug, created_at FROM memory_v3_selections
@@ -89,9 +89,18 @@ const log = getLogger("memory-v3-injection-gate");
89
89
  export const MEMORY_V3_INJECTION_GATE_CHECK_NAME = "memory_v3_injection_gate";
90
90
 
91
91
  /** Record one injection-gate run to usage telemetry. `detail` keys are the
92
- * platform-side contract (snake_case, scores and reason codes only — never
93
- * conversation content). Never throws: the gate is pass-open by design, and a
94
- * telemetry failure must not cost the turn its memory either. */
92
+ * platform-side contract (snake_case; scores, reason codes, and corpus-size
93
+ * counts only never page titles, slugs, or conversation content). Never
94
+ * throws: the gate is pass-open by design, and a telemetry failure must not
95
+ * cost the turn its memory either.
96
+ *
97
+ * `detail.scored` says whether checkV3Gate actually weighed scores this run, as
98
+ * opposed to the run taking a pass-open shortcut (dense off, dense unavailable,
99
+ * gate threw). Pass rate is only meaningful over scored runs — the shortcuts are
100
+ * all unconditional passes and would otherwise inflate it. It is derivable from
101
+ * `reason`, but the daemon owns the reason set and adds to it freely, so it is
102
+ * reported explicitly rather than left for the platform to re-derive from a
103
+ * hardcoded list that would silently miscount the next reason we add. */
95
104
  function recordGateRun(detail: Record<string, unknown>): void {
96
105
  try {
97
106
  recordWatchdogEvent({
@@ -105,6 +114,35 @@ function recordGateRun(detail: Record<string, unknown>): void {
105
114
  }
106
115
  }
107
116
 
117
+ /** `check_name` of the `watchdog` telemetry event recorded once per orchestrated
118
+ * turn, carrying what the SELECTOR did. Keep this string stable — the platform's
119
+ * memory admin analytics filter on it. */
120
+ export const MEMORY_V3_SELECTION_CHECK_NAME = "memory_v3_selection";
121
+
122
+ /** Record one turn's selection outcome. Deliberately a SEPARATE event from
123
+ * {@link MEMORY_V3_INJECTION_GATE_CHECK_NAME} rather than extra keys on it: the
124
+ * gate records at decision time, before selection has run, so folding the
125
+ * outcome in would mean deferring the gate event until after `selectPool` — and
126
+ * losing every gate run whenever selection throws, which is exactly the
127
+ * incident we would want the gate telemetry for. This event carries
128
+ * `gate_reason` so the two still group together without a join.
129
+ *
130
+ * `value` is the selection count. Never throws, same as the gate counter. */
131
+ function recordSelectionRun(
132
+ value: number,
133
+ detail: Record<string, unknown>,
134
+ ): void {
135
+ try {
136
+ recordWatchdogEvent({
137
+ checkName: MEMORY_V3_SELECTION_CHECK_NAME,
138
+ value,
139
+ detail,
140
+ });
141
+ } catch {
142
+ // Same contract as recordGateRun: telemetry must never cost a turn.
143
+ }
144
+ }
145
+
108
146
  /** Default number of BM25 needle articles to fold into the pool. */
109
147
  export const DEFAULT_NEEDLE_K = 12;
110
148
  /** Default number of dense-lane articles to fold into the pool. */
@@ -182,6 +220,12 @@ export interface OrchestrateDeps {
182
220
  * `enabled`, assembled in observeTurn). Omitted/disabled → the gate never
183
221
  * runs and every turn proceeds to selectPool as before. */
184
222
  gateConfig?: V3GateConfig;
223
+ /** Real concept-page count at lane build — the same corpus-size signal
224
+ * `resolveV3Tuning` switches the lean/full profile on. Reported with each
225
+ * gate run so the reason distribution can be read against
226
+ * `MEMORY_V3_FULL_PROFILE_MIN_PAGES`; omitted drops the field from the
227
+ * telemetry detail (the gate itself never reads it). */
228
+ realConceptPageCount?: number;
185
229
  }
186
230
 
187
231
  /** A finder-lane candidate: the slug, the descriptor that justified it, and
@@ -430,29 +474,90 @@ export async function orchestrate(
430
474
  //
431
475
  // The gate is dense-gated: it only runs when the live dense lane produced hits
432
476
  // (`liveDensed.length > 0`). In healthy operation dense returns top-k hits for
433
- // any query, so zero live dense hits means dense is unavailable denseK=0 (the
434
- // new-user/small-corpus profile), a degraded embedding backend, a Qdrant error
435
- // (`denseLaneScored` swallows these to `[]`), or only stale deleted-page points
436
- // returned NOT low relevance. checkV3Gate reads only finder scores and cannot
437
- // tell those apart, so without live dense signal we pass open rather than
438
- // suppress all memory (not even the core/hot/fresh prefix) on every
439
- // lexically-weak turn.
477
+ // any query, so zero live dense hits means dense is unavailable, NOT low
478
+ // relevance. checkV3Gate reads only finder scores and cannot tell those apart,
479
+ // so without live dense signal we pass open rather than suppress all memory
480
+ // (not even the core/hot/fresh prefix) on every lexically-weak turn.
481
+ //
482
+ // Two very different situations reach that pass-open, and they carry distinct
483
+ // reason codes because only one of them is a problem:
484
+ // - dense_disabled: `denseK === 0`, so the lane never ran. The lean
485
+ // new-user profile (`MEMORY_V3_NEW_USER_TUNING`) sets it
486
+ // for sub-threshold corpora, and that same profile sets
487
+ // `selectorEnabled: false` — so there is no selectPool
488
+ // call for the gate to save and passing open is free.
489
+ // Expected, benign, and the bulk of gate runs.
490
+ // - dense_unavailable: the lane ran and yielded nothing live — a degraded
491
+ // embedding backend, a Qdrant error (`denseLaneScored`
492
+ // swallows these to `[]`), or only stale deleted-page
493
+ // points. Dense SHOULD have scored this turn and did
494
+ // not, so this one is worth alerting on.
495
+ //
496
+ // Every run carries the corpus size, so the reason mix can be read against the
497
+ // profile threshold rather than guessed at: `dense_disabled` is sub-threshold
498
+ // by construction, and whether those assistants sit at 1 page or 9 is the
499
+ // difference between "empty" and "about to cross".
500
+ const recordGate = (detail: Record<string, unknown>): void =>
501
+ recordGateRun(
502
+ deps.realConceptPageCount === undefined
503
+ ? detail
504
+ : { ...detail, real_concept_page_count: deps.realConceptPageCount },
505
+ );
506
+
507
+ // This turn's gate decision, carried to the selection event so the two group
508
+ // together without a join. Null when the gate never ran (disabled/omitted).
509
+ let gateOutcome: { reason: string; pass: boolean } | null = null;
510
+
511
+ // What the SELECTOR did with the pool the gate let through — the outcome the
512
+ // gate's own pass rate cannot see. A passed gate only means selectPool got to
513
+ // run; the selector is told to return `[]` when no candidate is relevant, so
514
+ // it is the real injection decision and the pass rate is an upper bound on it.
515
+ //
516
+ // `selector_ran` marks the turns where the selector actually JUDGED the pool,
517
+ // and is the filter that keeps a relevance rate honest. Three ways a turn can
518
+ // report zero selections without the selector having been asked, all of which
519
+ // must stay out of that rate:
520
+ // - the lean profile sets `selectorEnabled: false`, so
521
+ // `selectAllPoolCandidates` returns the whole pool untouched (would read
522
+ // as a 100% hit rate),
523
+ // - a closed gate hard-skips selection entirely (a 0%),
524
+ // - the pool is empty, and `selectPool` returns `[]` before it ever reaches
525
+ // the provider (also a 0%).
526
+ // The last is why `poolSize` decides this rather than each call site: an empty
527
+ // pool is not a judgment that nothing was relevant, and no caller has to
528
+ // remember that.
529
+ const recordSelection = (
530
+ selections: SelectedPage[],
531
+ poolSize: number,
532
+ ): void => {
533
+ const detail: Record<string, unknown> = {
534
+ gate_reason: gateOutcome?.reason ?? null,
535
+ gate_pass: gateOutcome?.pass ?? null,
536
+ selector_ran: deps.selectorEnabled !== false && poolSize > 0,
537
+ selected_count: selections.length,
538
+ pool_size: poolSize,
539
+ };
540
+ if (deps.realConceptPageCount !== undefined) {
541
+ detail.real_concept_page_count = deps.realConceptPageCount;
542
+ }
543
+ recordSelectionRun(selections.length, detail);
544
+ };
440
545
  if (deps.gateConfig?.enabled) {
441
546
  if (liveDensed.length === 0) {
442
- // No live dense hits (denseK=0 / degraded backend / Qdrant error swallowed
443
- // to [] / only stale deleted-page points returned). That means dense is
444
- // unavailable, not low relevance, so pass open — but record it so rollout
445
- // telemetry sees these turns instead of silently skipping the gate.
547
+ const reason = denseEnabled ? "dense_unavailable" : "dense_disabled";
446
548
  log.info(
447
549
  {
448
550
  conversationId: turn.conversationId,
449
551
  turnNumber: turn.turnNumber,
450
- reason: "dense_unavailable",
552
+ reason,
451
553
  pass: true,
452
554
  },
453
- "memory-v3 injection gate: dense lane unavailable, passing open",
555
+ denseEnabled
556
+ ? "memory-v3 injection gate: dense lane unavailable, passing open"
557
+ : "memory-v3 injection gate: dense lane disabled, passing open",
454
558
  );
455
- recordGateRun({ pass: true, reason: "dense_unavailable" });
559
+ gateOutcome = { reason, pass: true };
560
+ recordGate({ pass: true, reason, scored: false });
456
561
  } else {
457
562
  let gate: V3GateResult | null = null;
458
563
  try {
@@ -469,7 +574,8 @@ export async function orchestrate(
469
574
  },
470
575
  "memory-v3 injection gate threw; passing open (proceeding with selection)",
471
576
  );
472
- recordGateRun({ pass: true, reason: "gate_error" });
577
+ gateOutcome = { reason: "gate_error", pass: true };
578
+ recordGate({ pass: true, reason: "gate_error", scored: false });
473
579
  }
474
580
  if (gate) {
475
581
  log.info(
@@ -480,9 +586,11 @@ export async function orchestrate(
480
586
  },
481
587
  "memory-v3 injection gate decision",
482
588
  );
483
- recordGateRun({
589
+ gateOutcome = { reason: gate.reason, pass: gate.pass };
590
+ recordGate({
484
591
  pass: gate.pass,
485
592
  reason: gate.reason,
593
+ scored: true,
486
594
  top_dense_score: gate.topDenseScore,
487
595
  top_norm_sparse_score: gate.topNormSparseScore,
488
596
  checked_articles: gate.checkedArticles,
@@ -505,10 +613,18 @@ export async function orchestrate(
505
613
  // explicitly configured with `selectorEnabled: false` AND
506
614
  // `denseK > 0` (the dense-gated gate only runs with dense hits; the
507
615
  // new-user profile sets `denseK: 0`, so the gate never runs for it).
508
- return closed(
509
- await runSelection({ stable: buildStable(), finder: [] }),
510
- );
616
+ const stableOnly = buildStable();
617
+ const bypassed = await runSelection({
618
+ stable: stableOnly,
619
+ finder: [],
620
+ });
621
+ recordSelection(bypassed, stableOnly.length);
622
+ return closed(bypassed);
511
623
  }
624
+ // Hard skip: the selector is never consulted, so this is a zero
625
+ // selection BY CONSTRUCTION, not a judgment that nothing was relevant.
626
+ // `selector_ran: false` keeps it out of any relevance rate.
627
+ recordSelection([], 0);
512
628
  return closed([]);
513
629
  }
514
630
  }
@@ -602,6 +718,7 @@ export async function orchestrate(
602
718
  // scaffold; `undefined` falls through to the bundled default.
603
719
  const pool = { stable, finder: finderTail };
604
720
  const selections = await runSelection(pool);
721
+ recordSelection(selections, stable.length + finderTail.length);
605
722
 
606
723
  return {
607
724
  selections,
@@ -31,8 +31,8 @@ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
31
31
  import type { Message } from "@vellumai/plugin-api";
32
32
  import { drizzle } from "drizzle-orm/bun-sqlite";
33
33
 
34
- import { migrateAddMemoryV3Selections } from "../../../../persistence/migrations/268-add-memory-v3-selections.js";
35
34
  import { migrateAddMemoryV3EverInjected } from "../../../../persistence/migrations/277-add-memory-v3-ever-injected.js";
35
+ import { ensureMemoryV3SelectionsSchema } from "../../../../persistence/migrations/338-move-memory-v3-selections-to-memory-db.js";
36
36
  import * as schema from "../../../../persistence/schema/index.js";
37
37
  import { wrapMemoryBlock } from "../memory-marker.js";
38
38
 
@@ -48,12 +48,15 @@ let pruneConfig: {
48
48
  } | null = null;
49
49
 
50
50
  let testSqlite: Database;
51
+ // `planPrune`'s recency ranking reads `memory_v3_selections` over the
52
+ // dedicated memory connection, resolved via `getMemorySqlite` — stubbed to a
53
+ // second in-memory DB carrying the relocated table's schema.
54
+ let memorySqlite: Database;
51
55
  let testDb = makeDb();
52
56
  function makeDb() {
53
57
  testSqlite = new Database(":memory:");
54
58
  const db = drizzle(testSqlite, { schema });
55
59
  migrateAddMemoryV3EverInjected(db);
56
- migrateAddMemoryV3Selections(db);
57
60
  // Minimal `messages` shape — `collectPersistedV3Cards` reads only
58
61
  // `conversation_id` and `metadata`.
59
62
  testSqlite.run(/*sql*/ `
@@ -66,6 +69,8 @@ function makeDb() {
66
69
  created_at INTEGER NOT NULL
67
70
  )
68
71
  `);
72
+ memorySqlite = new Database(":memory:");
73
+ ensureMemoryV3SelectionsSchema(memorySqlite);
69
74
  return db;
70
75
  }
71
76
 
@@ -76,6 +81,8 @@ mock.module("../../../../persistence/db-connection.js", () => ({
76
81
  pruneMockActive
77
82
  ? testSqlite
78
83
  : realDb.getSqliteFrom(db as Parameters<typeof realDb.getSqliteFrom>[0]),
84
+ getMemorySqlite: () =>
85
+ pruneMockActive ? memorySqlite : realDb.getMemorySqlite(),
79
86
  }));
80
87
 
81
88
  // Memory code resolves its config through the plugin's own accessor
@@ -120,7 +127,7 @@ function insertSelection(
120
127
  slug: string,
121
128
  createdAt: number,
122
129
  ): void {
123
- testSqlite
130
+ memorySqlite
124
131
  .query(
125
132
  /*sql*/ `
126
133
  INSERT OR REPLACE INTO memory_v3_selections
@@ -63,6 +63,7 @@ import type { ContentBlock, Message } from "@vellumai/plugin-api";
63
63
  import { getDb, getSqliteFrom } from "../../../../persistence/db-connection.js";
64
64
  import { getMemoryConfig } from "../config.js";
65
65
  import { getLogger } from "../logging.js";
66
+ import { memorySqliteOrNull } from "../memory-db.js";
66
67
  import { unwrapMemoryBlock, wrapMemoryBlock } from "../memory-marker.js";
67
68
  import {
68
69
  INJECTED_CONCEPT_HEADER_REGEX,
@@ -210,12 +211,14 @@ export interface PruneDeps {
210
211
  *
211
212
  * The footprint and the candidates both range over the ACTIVE injected slugs.
212
213
  * Candidates are ranked by last selection recency — `MAX(created_at)` per
213
- * slug from `memory_v3_selections`, falling back to the store's `injected_at`
214
- * for slugs with no selection rows (e.g. rows copied by a full fork) — taken
215
- * oldest-first until the footprint is at `targetResidentBytes`. Core/hot lane
216
- * members are exempt, and zero-byte rows (capability slugs, truncated-fork
217
- * seeds: dedup-only, no byte accounting) are skipped — pruning them frees
218
- * nothing while discarding inherited context.
214
+ * slug from `memory_v3_selections` (read over the dedicated memory
215
+ * connection; an unavailable memory database reads as no selection rows),
216
+ * falling back to the store's `injected_at` for slugs with no selection rows
217
+ * (e.g. rows copied by a full fork) taken oldest-first until the footprint
218
+ * is at `targetResidentBytes`. Core/hot lane members are exempt, and
219
+ * zero-byte rows (capability slugs, truncated-fork seeds: dedup-only, no byte
220
+ * accounting) are skipped — pruning them frees nothing while discarding
221
+ * inherited context.
219
222
  */
220
223
  export function planPrune(
221
224
  deps: PruneDeps,
@@ -225,15 +228,18 @@ export function planPrune(
225
228
  const resident = activeEntries.reduce((sum, e) => sum + e.bytes, 0);
226
229
  if (resident <= deps.maxResidentBytes) return null;
227
230
 
228
- const selectionRows = getSqliteFrom(getDb())
229
- .query(
230
- /*sql*/ `
231
+ const memoryRaw = memorySqliteOrNull("planPrune");
232
+ const selectionRows = memoryRaw
233
+ ? (memoryRaw
234
+ .query(
235
+ /*sql*/ `
231
236
  SELECT slug, MAX(created_at) AS lastSelectedAt FROM memory_v3_selections
232
237
  WHERE conversation_id = ?
233
238
  GROUP BY slug
234
239
  `,
235
- )
236
- .all(conversationId) as Array<{ slug: string; lastSelectedAt: number }>;
240
+ )
241
+ .all(conversationId) as Array<{ slug: string; lastSelectedAt: number }>)
242
+ : [];
237
243
  const lastSelectedAt = new Map(
238
244
  selectionRows.map((row) => [row.slug, row.lastSelectedAt]),
239
245
  );