@vellumai/assistant 0.4.49 → 0.4.51

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 (353) hide show
  1. package/ARCHITECTURE.md +24 -33
  2. package/README.md +3 -3
  3. package/docs/architecture/integrations.md +2 -2
  4. package/docs/architecture/keychain-broker.md +6 -6
  5. package/docs/architecture/memory.md +180 -119
  6. package/knip.json +32 -0
  7. package/package.json +3 -2
  8. package/src/__tests__/agent-loop.test.ts +3 -1
  9. package/src/__tests__/anthropic-provider.test.ts +114 -23
  10. package/src/__tests__/approval-cascade.test.ts +1 -15
  11. package/src/__tests__/approval-routes-http.test.ts +2 -0
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  13. package/src/__tests__/btw-routes.test.ts +61 -5
  14. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  15. package/src/__tests__/checker.test.ts +13 -0
  16. package/src/__tests__/config-schema.test.ts +1 -68
  17. package/src/__tests__/config-watcher.test.ts +8 -0
  18. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  19. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  20. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  21. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  22. package/src/__tests__/credential-security-invariants.test.ts +8 -7
  23. package/src/__tests__/credential-vault-unit.test.ts +23 -18
  24. package/src/__tests__/credential-vault.test.ts +30 -18
  25. package/src/__tests__/credentials-cli.test.ts +257 -82
  26. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  27. package/src/__tests__/date-context.test.ts +93 -77
  28. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  29. package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
  30. package/src/__tests__/history-repair.test.ts +245 -0
  31. package/src/__tests__/host-cu-proxy.test.ts +165 -3
  32. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  33. package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
  34. package/src/__tests__/integration-status.test.ts +31 -30
  35. package/src/__tests__/invite-redemption-service.test.ts +166 -13
  36. package/src/__tests__/invite-routes-http.test.ts +166 -5
  37. package/src/__tests__/keychain-broker-client.test.ts +4 -4
  38. package/src/__tests__/list-messages-attachments.test.ts +193 -0
  39. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  40. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  41. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  42. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  43. package/src/__tests__/memory-regressions.test.ts +477 -2841
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  45. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  46. package/src/__tests__/mime-builder.test.ts +28 -0
  47. package/src/__tests__/native-web-search.test.ts +1 -0
  48. package/src/__tests__/oauth-cli.test.ts +824 -31
  49. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  50. package/src/__tests__/oauth-store.test.ts +363 -17
  51. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  52. package/src/__tests__/registry.test.ts +0 -1
  53. package/src/__tests__/relay-server.test.ts +55 -1
  54. package/src/__tests__/schedule-tools.test.ts +32 -0
  55. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  56. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  57. package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
  58. package/src/__tests__/secure-keys.test.ts +78 -18
  59. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  60. package/src/__tests__/server-history-render.test.ts +2 -2
  61. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  62. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  63. package/src/__tests__/session-agent-loop.test.ts +19 -15
  64. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  65. package/src/__tests__/session-error.test.ts +124 -2
  66. package/src/__tests__/session-history-web-search.test.ts +918 -0
  67. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  68. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  69. package/src/__tests__/session-queue.test.ts +37 -27
  70. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  71. package/src/__tests__/session-slash-known.test.ts +1 -15
  72. package/src/__tests__/session-slash-queue.test.ts +1 -15
  73. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  74. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  75. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  76. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  77. package/src/__tests__/skills-install-extract.test.ts +93 -0
  78. package/src/__tests__/skills.test.ts +2 -2
  79. package/src/__tests__/skillssh-registry.test.ts +451 -0
  80. package/src/__tests__/slack-channel-config.test.ts +10 -8
  81. package/src/__tests__/trust-store.test.ts +15 -0
  82. package/src/__tests__/twilio-config.test.ts +11 -10
  83. package/src/__tests__/twilio-provider.test.ts +9 -4
  84. package/src/__tests__/voice-invite-redemption.test.ts +85 -5
  85. package/src/agent/ax-tree-compaction.test.ts +51 -0
  86. package/src/agent/loop.ts +39 -12
  87. package/src/approvals/AGENTS.md +1 -1
  88. package/src/approvals/guardian-request-resolvers.ts +14 -2
  89. package/src/bundler/compiler-tools.ts +66 -2
  90. package/src/calls/call-domain.ts +134 -3
  91. package/src/calls/call-store.ts +6 -0
  92. package/src/calls/relay-server.ts +44 -6
  93. package/src/calls/relay-setup-router.ts +17 -1
  94. package/src/calls/twilio-config.ts +5 -4
  95. package/src/calls/twilio-provider.ts +14 -9
  96. package/src/calls/twilio-rest.ts +10 -7
  97. package/src/calls/types.ts +3 -1
  98. package/src/cli/commands/config.ts +14 -9
  99. package/src/cli/commands/contacts.ts +3 -0
  100. package/src/cli/commands/credentials.ts +170 -174
  101. package/src/cli/commands/doctor.ts +11 -8
  102. package/src/cli/commands/keys.ts +9 -9
  103. package/src/cli/commands/mcp.ts +46 -59
  104. package/src/cli/commands/memory.ts +16 -165
  105. package/src/cli/commands/oauth/apps.ts +68 -10
  106. package/src/cli/commands/oauth/connections.ts +475 -105
  107. package/src/cli/commands/oauth/index.ts +3 -3
  108. package/src/cli/commands/oauth/providers.ts +18 -4
  109. package/src/cli/commands/sessions.ts +5 -2
  110. package/src/cli/commands/skills.ts +173 -1
  111. package/src/cli/http-client.ts +0 -20
  112. package/src/cli/main-screen.tsx +2 -2
  113. package/src/cli/program.ts +5 -6
  114. package/src/cli.ts +20 -22
  115. package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
  116. package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
  117. package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +35 -11
  119. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  120. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  121. package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
  135. package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
  136. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
  137. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
  138. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
  139. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
  140. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
  141. package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
  142. package/src/config/bundled-skills/messaging/SKILL.md +1 -1
  143. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  144. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  145. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
  146. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
  148. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
  149. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
  150. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
  151. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
  152. package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
  153. package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
  154. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
  155. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
  156. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
  157. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
  158. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
  159. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
  160. package/src/config/bundled-tool-registry.ts +2 -5
  161. package/src/config/loader.ts +6 -42
  162. package/src/config/schema.ts +1 -12
  163. package/src/config/schemas/memory-lifecycle.ts +0 -9
  164. package/src/config/schemas/memory-processing.ts +0 -180
  165. package/src/config/schemas/memory-retrieval.ts +32 -104
  166. package/src/config/schemas/memory.ts +0 -10
  167. package/src/config/types.ts +0 -4
  168. package/src/contacts/contact-store.ts +39 -2
  169. package/src/contacts/contacts-write.ts +9 -0
  170. package/src/context/window-manager.ts +4 -1
  171. package/src/daemon/config-watcher.ts +55 -2
  172. package/src/daemon/daemon-control.ts +1 -1
  173. package/src/daemon/date-context.ts +114 -31
  174. package/src/daemon/handlers/config-ingress.ts +2 -2
  175. package/src/daemon/handlers/config-slack-channel.ts +59 -39
  176. package/src/daemon/handlers/config-telegram.ts +23 -14
  177. package/src/daemon/handlers/session-history.ts +1 -358
  178. package/src/daemon/handlers/sessions.ts +18 -13
  179. package/src/daemon/handlers/shared.ts +3 -17
  180. package/src/daemon/handlers/skills.ts +20 -1
  181. package/src/daemon/history-repair.ts +72 -8
  182. package/src/daemon/host-cu-proxy.ts +55 -26
  183. package/src/daemon/lifecycle.ts +39 -4
  184. package/src/daemon/mcp-reload-service.ts +2 -2
  185. package/src/daemon/message-types/computer-use.ts +1 -12
  186. package/src/daemon/message-types/memory.ts +4 -16
  187. package/src/daemon/message-types/messages.ts +1 -0
  188. package/src/daemon/message-types/sessions.ts +4 -42
  189. package/src/daemon/server.ts +6 -1
  190. package/src/daemon/session-agent-loop-handlers.ts +38 -0
  191. package/src/daemon/session-agent-loop.ts +334 -48
  192. package/src/daemon/session-error.ts +89 -6
  193. package/src/daemon/session-history.ts +17 -7
  194. package/src/daemon/session-media-retry.ts +6 -2
  195. package/src/daemon/session-memory.ts +69 -149
  196. package/src/daemon/session-process.ts +10 -1
  197. package/src/daemon/session-runtime-assembly.ts +49 -19
  198. package/src/daemon/session-slash.ts +3 -5
  199. package/src/daemon/session-surfaces.ts +4 -1
  200. package/src/daemon/session-tool-setup.ts +7 -1
  201. package/src/daemon/session.ts +12 -2
  202. package/src/email/providers/index.ts +2 -2
  203. package/src/instrument.ts +61 -1
  204. package/src/media/avatar-router.ts +1 -1
  205. package/src/memory/admin.ts +2 -191
  206. package/src/memory/canonical-guardian-store.ts +38 -2
  207. package/src/memory/conversation-crud.ts +0 -33
  208. package/src/memory/conversation-queries.ts +25 -83
  209. package/src/memory/db-init.ts +32 -0
  210. package/src/memory/embedding-backend.ts +84 -8
  211. package/src/memory/embedding-types.ts +9 -1
  212. package/src/memory/indexer.ts +7 -46
  213. package/src/memory/invite-store.ts +19 -0
  214. package/src/memory/items-extractor.ts +274 -76
  215. package/src/memory/job-handlers/backfill.ts +2 -127
  216. package/src/memory/job-handlers/cleanup.ts +2 -16
  217. package/src/memory/job-handlers/extraction.ts +2 -138
  218. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  219. package/src/memory/job-handlers/summarization.ts +3 -148
  220. package/src/memory/job-utils.ts +21 -59
  221. package/src/memory/jobs-store.ts +1 -159
  222. package/src/memory/jobs-worker.ts +9 -52
  223. package/src/memory/migrations/104-core-indexes.ts +3 -3
  224. package/src/memory/migrations/149-oauth-tables.ts +2 -0
  225. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  226. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  227. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  228. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  229. package/src/memory/migrations/154-drop-fts.ts +20 -0
  230. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  231. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  232. package/src/memory/migrations/157-invite-contact-id.ts +104 -0
  233. package/src/memory/migrations/index.ts +8 -0
  234. package/src/memory/migrations/registry.ts +6 -0
  235. package/src/memory/qdrant-client.ts +148 -51
  236. package/src/memory/raw-query.ts +1 -1
  237. package/src/memory/retriever.test.ts +294 -273
  238. package/src/memory/retriever.ts +421 -645
  239. package/src/memory/schema/calls.ts +2 -0
  240. package/src/memory/schema/contacts.ts +1 -0
  241. package/src/memory/schema/memory-core.ts +3 -48
  242. package/src/memory/schema/oauth.ts +2 -0
  243. package/src/memory/search/formatting.ts +263 -176
  244. package/src/memory/search/lexical.ts +1 -254
  245. package/src/memory/search/ranking.ts +0 -455
  246. package/src/memory/search/semantic.ts +100 -14
  247. package/src/memory/search/staleness.ts +47 -0
  248. package/src/memory/search/tier-classifier.ts +21 -0
  249. package/src/memory/search/types.ts +15 -77
  250. package/src/memory/task-memory-cleanup.ts +4 -6
  251. package/src/messaging/provider.ts +1 -1
  252. package/src/messaging/providers/gmail/adapter.ts +1 -1
  253. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  254. package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
  255. package/src/messaging/providers/whatsapp/adapter.ts +13 -9
  256. package/src/messaging/registry.ts +9 -5
  257. package/src/oauth/byo-connection.test.ts +40 -25
  258. package/src/oauth/connect-orchestrator.ts +4 -10
  259. package/src/oauth/connection-resolver.ts +20 -6
  260. package/src/oauth/manual-token-connection.ts +5 -5
  261. package/src/oauth/oauth-store.ts +183 -31
  262. package/src/oauth/platform-connection.test.ts +1 -1
  263. package/src/oauth/provider-behaviors.ts +503 -4
  264. package/src/oauth/seed-providers.ts +214 -8
  265. package/src/oauth/token-persistence.ts +31 -16
  266. package/src/permissions/defaults.ts +1 -0
  267. package/src/permissions/trust-store.ts +23 -1
  268. package/src/playbooks/playbook-compiler.ts +1 -1
  269. package/src/prompts/system-prompt.ts +18 -2
  270. package/src/providers/anthropic/client.ts +56 -126
  271. package/src/providers/types.ts +7 -1
  272. package/src/runtime/AGENTS.md +9 -0
  273. package/src/runtime/auth/route-policy.ts +6 -3
  274. package/src/runtime/channel-readiness-service.ts +48 -40
  275. package/src/runtime/guardian-reply-router.ts +24 -22
  276. package/src/runtime/http-server.ts +2 -2
  277. package/src/runtime/http-types.ts +2 -0
  278. package/src/runtime/invite-redemption-service.ts +72 -12
  279. package/src/runtime/invite-service.ts +43 -0
  280. package/src/runtime/middleware/twilio-validation.ts +1 -1
  281. package/src/runtime/pending-interactions.ts +2 -2
  282. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  283. package/src/runtime/routes/btw-routes.ts +10 -5
  284. package/src/runtime/routes/conversation-routes.ts +56 -11
  285. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  286. package/src/runtime/routes/integrations/slack/channel.ts +2 -2
  287. package/src/runtime/routes/integrations/telegram.ts +2 -2
  288. package/src/runtime/routes/integrations/twilio.ts +17 -17
  289. package/src/runtime/routes/invite-routes.ts +29 -4
  290. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  291. package/src/runtime/routes/memory-item-routes.ts +503 -0
  292. package/src/runtime/routes/secret-routes.ts +17 -0
  293. package/src/runtime/routes/session-management-routes.ts +3 -3
  294. package/src/runtime/routes/settings-routes.ts +3 -3
  295. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  296. package/src/runtime/routes/workspace-routes.ts +9 -4
  297. package/src/runtime/routes/workspace-utils.ts +8 -2
  298. package/src/schedule/integration-status.ts +26 -19
  299. package/src/security/keychain-broker-client.ts +17 -4
  300. package/src/security/oauth2.ts +6 -7
  301. package/src/security/secure-keys.ts +44 -19
  302. package/src/security/token-manager.ts +46 -39
  303. package/src/services/vercel-deploy.ts +0 -24
  304. package/src/signals/confirm.ts +78 -0
  305. package/src/signals/mcp-reload.ts +18 -0
  306. package/src/skills/catalog-install.ts +74 -18
  307. package/src/skills/skillssh-registry.ts +503 -0
  308. package/src/tools/assets/search.ts +5 -1
  309. package/src/tools/computer-use/definitions.ts +0 -10
  310. package/src/tools/computer-use/registry.ts +1 -1
  311. package/src/tools/credentials/vault.ts +22 -7
  312. package/src/tools/memory/definitions.ts +4 -13
  313. package/src/tools/memory/handlers.test.ts +83 -103
  314. package/src/tools/memory/handlers.ts +50 -85
  315. package/src/tools/network/script-proxy/session-manager.ts +8 -8
  316. package/src/tools/schedule/create.ts +10 -3
  317. package/src/tools/schedule/update.ts +8 -1
  318. package/src/tools/skills/load.ts +25 -2
  319. package/src/watcher/provider-types.ts +1 -1
  320. package/src/watcher/providers/github.ts +1 -1
  321. package/src/watcher/providers/gmail.ts +3 -3
  322. package/src/watcher/providers/google-calendar.ts +3 -3
  323. package/src/watcher/providers/linear.ts +1 -1
  324. package/src/__tests__/clarification-resolver.test.ts +0 -193
  325. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  326. package/src/__tests__/conflict-policy.test.ts +0 -269
  327. package/src/__tests__/conflict-store.test.ts +0 -372
  328. package/src/__tests__/contradiction-checker.test.ts +0 -361
  329. package/src/__tests__/entity-extractor.test.ts +0 -211
  330. package/src/__tests__/entity-search.test.ts +0 -1117
  331. package/src/__tests__/profile-compiler.test.ts +0 -392
  332. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  333. package/src/__tests__/session-profile-injection.test.ts +0 -557
  334. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  335. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  336. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  337. package/src/daemon/session-conflict-gate.ts +0 -167
  338. package/src/daemon/session-dynamic-profile.ts +0 -77
  339. package/src/memory/clarification-resolver.ts +0 -417
  340. package/src/memory/conflict-intent.ts +0 -205
  341. package/src/memory/conflict-policy.ts +0 -127
  342. package/src/memory/conflict-store.ts +0 -410
  343. package/src/memory/contradiction-checker.ts +0 -508
  344. package/src/memory/entity-extractor.ts +0 -535
  345. package/src/memory/format-recall.ts +0 -47
  346. package/src/memory/fts-reconciler.ts +0 -165
  347. package/src/memory/job-handlers/conflict.ts +0 -200
  348. package/src/memory/profile-compiler.ts +0 -195
  349. package/src/memory/recall-cache.ts +0 -117
  350. package/src/memory/search/entity.ts +0 -535
  351. package/src/memory/search/query-expansion.test.ts +0 -70
  352. package/src/memory/search/query-expansion.ts +0 -118
  353. package/src/runtime/routes/mcp-routes.ts +0 -20
@@ -1,165 +0,0 @@
1
- import { getLogger } from "../util/logger.js";
2
- import { rawGet, rawRun } from "./db.js";
3
-
4
- const log = getLogger("fts-reconciler");
5
-
6
- export interface FtsReconciliationResult {
7
- table: string;
8
- baseCount: number;
9
- ftsCount: number;
10
- missingInserted: number;
11
- orphansRemoved: number;
12
- staleRefreshed: number;
13
- }
14
-
15
- /**
16
- * Reconcile a single FTS index against its base table. Detects missing entries
17
- * (rows in the base table with no corresponding FTS row) and orphaned entries
18
- * (FTS rows whose base table row no longer exists), then repairs both.
19
- *
20
- * This is lighter than a full rebuild — it only touches the delta rather than
21
- * wiping and re-inserting the entire index.
22
- */
23
- function reconcileTable(opts: {
24
- ftsTable: string;
25
- ftsIdColumn: string;
26
- ftsContentColumn: string;
27
- baseTable: string;
28
- baseIdColumn: string;
29
- baseContentColumn: string;
30
- }): FtsReconciliationResult {
31
- const {
32
- ftsTable,
33
- ftsIdColumn,
34
- ftsContentColumn,
35
- baseTable,
36
- baseIdColumn,
37
- baseContentColumn,
38
- } = opts;
39
-
40
- const baseCount = (
41
- rawGet<{ c: number }>(`SELECT COUNT(*) AS c FROM ${baseTable}`) ?? { c: 0 }
42
- ).c;
43
- const ftsCount = (
44
- rawGet<{ c: number }>(`SELECT COUNT(*) AS c FROM ${ftsTable}`) ?? { c: 0 }
45
- ).c;
46
-
47
- // Find base table rows missing from the FTS index
48
- const missingInserted = rawRun(/*sql*/ `
49
- INSERT INTO ${ftsTable}(${ftsIdColumn}, ${ftsContentColumn})
50
- SELECT b.${baseIdColumn}, b.${baseContentColumn}
51
- FROM ${baseTable} b
52
- LEFT JOIN ${ftsTable} f ON f.${ftsIdColumn} = b.${baseIdColumn}
53
- WHERE f.${ftsIdColumn} IS NULL
54
- `);
55
-
56
- // Find FTS rows whose base table row no longer exists
57
- const orphansRemoved = rawRun(/*sql*/ `
58
- DELETE FROM ${ftsTable}
59
- WHERE ${ftsIdColumn} IN (
60
- SELECT f.${ftsIdColumn}
61
- FROM ${ftsTable} f
62
- LEFT JOIN ${baseTable} b ON b.${baseIdColumn} = f.${ftsIdColumn}
63
- WHERE b.${baseIdColumn} IS NULL
64
- )
65
- `);
66
-
67
- // Refresh FTS rows whose content is stale (base row was updated but the
68
- // update trigger didn't fire or was missing). Delete-then-insert is the
69
- // standard FTS5 update pattern.
70
- const staleDeleted = rawRun(/*sql*/ `
71
- DELETE FROM ${ftsTable}
72
- WHERE ${ftsIdColumn} IN (
73
- SELECT f.${ftsIdColumn}
74
- FROM ${ftsTable} f
75
- JOIN ${baseTable} b ON b.${baseIdColumn} = f.${ftsIdColumn}
76
- WHERE b.${baseContentColumn} IS NOT f.${ftsContentColumn}
77
- )
78
- `);
79
- if (staleDeleted > 0) {
80
- rawRun(/*sql*/ `
81
- INSERT INTO ${ftsTable}(${ftsIdColumn}, ${ftsContentColumn})
82
- SELECT b.${baseIdColumn}, b.${baseContentColumn}
83
- FROM ${baseTable} b
84
- LEFT JOIN ${ftsTable} f ON f.${ftsIdColumn} = b.${baseIdColumn}
85
- WHERE f.${ftsIdColumn} IS NULL
86
- `);
87
- }
88
-
89
- return {
90
- table: ftsTable,
91
- baseCount,
92
- ftsCount,
93
- missingInserted,
94
- orphansRemoved,
95
- staleRefreshed: staleDeleted,
96
- };
97
- }
98
-
99
- /**
100
- * Reconcile all FTS indexes. Returns results for each table so callers can
101
- * inspect what was repaired.
102
- */
103
- export function reconcileFtsIndexes(): FtsReconciliationResult[] {
104
- const results: FtsReconciliationResult[] = [];
105
- const errors: unknown[] = [];
106
-
107
- // memory_segment_fts tracks memory_segments
108
- try {
109
- const memResult = reconcileTable({
110
- ftsTable: "memory_segment_fts",
111
- ftsIdColumn: "segment_id",
112
- ftsContentColumn: "text",
113
- baseTable: "memory_segments",
114
- baseIdColumn: "id",
115
- baseContentColumn: "text",
116
- });
117
- results.push(memResult);
118
- if (
119
- memResult.missingInserted > 0 ||
120
- memResult.orphansRemoved > 0 ||
121
- memResult.staleRefreshed > 0
122
- ) {
123
- log.info(memResult, "Reconciled memory_segment_fts");
124
- } else {
125
- log.debug(memResult, "memory_segment_fts is in sync");
126
- }
127
- } catch (err) {
128
- log.error({ err }, "Failed to reconcile memory_segment_fts");
129
- errors.push(err);
130
- }
131
-
132
- // messages_fts tracks messages
133
- try {
134
- const msgResult = reconcileTable({
135
- ftsTable: "messages_fts",
136
- ftsIdColumn: "message_id",
137
- ftsContentColumn: "content",
138
- baseTable: "messages",
139
- baseIdColumn: "id",
140
- baseContentColumn: "content",
141
- });
142
- results.push(msgResult);
143
- if (
144
- msgResult.missingInserted > 0 ||
145
- msgResult.orphansRemoved > 0 ||
146
- msgResult.staleRefreshed > 0
147
- ) {
148
- log.info(msgResult, "Reconciled messages_fts");
149
- } else {
150
- log.debug(msgResult, "messages_fts is in sync");
151
- }
152
- } catch (err) {
153
- log.error({ err }, "Failed to reconcile messages_fts");
154
- errors.push(err);
155
- }
156
-
157
- if (errors.length > 0) {
158
- throw new AggregateError(
159
- errors,
160
- `FTS reconciliation failed for ${errors.length} table(s)`,
161
- );
162
- }
163
-
164
- return results;
165
- }
@@ -1,200 +0,0 @@
1
- import { and, asc, eq, inArray, lt, ne } from "drizzle-orm";
2
-
3
- import type { AssistantConfig } from "../../config/types.js";
4
- import { getLogger } from "../../util/logger.js";
5
- import { resolveConflictClarification } from "../clarification-resolver.js";
6
- import {
7
- areStatementsCoherent,
8
- computeConflictRelevance,
9
- looksLikeClarificationReply,
10
- shouldAttemptConflictResolution,
11
- } from "../conflict-intent.js";
12
- import {
13
- isConflictKindPairEligible,
14
- isConflictUserEvidenced,
15
- isStatementConflictEligible,
16
- } from "../conflict-policy.js";
17
- import {
18
- applyConflictResolution,
19
- listPendingConflictDetails,
20
- resolveConflict,
21
- } from "../conflict-store.js";
22
- import { getDb } from "../db.js";
23
- import { asPositiveMs, asString } from "../job-utils.js";
24
- import { enqueueMemoryJob, type MemoryJob } from "../jobs-store.js";
25
- import { extractTextFromStoredMessageContent } from "../message-content.js";
26
- import { memoryItemConflicts, messages } from "../schema.js";
27
-
28
- const log = getLogger("memory-jobs-worker");
29
-
30
- const CLEANUP_BATCH_LIMIT = 250;
31
-
32
- export async function resolvePendingConflictsForMessageJob(
33
- job: MemoryJob,
34
- config: AssistantConfig,
35
- ): Promise<void> {
36
- if (!config.memory.conflicts.enabled) return;
37
- const messageId = asString(job.payload.messageId);
38
- if (!messageId) return;
39
- const scopeId = asString(job.payload.scopeId) ?? "default";
40
- const db = getDb();
41
- const message = db
42
- .select({
43
- id: messages.id,
44
- role: messages.role,
45
- content: messages.content,
46
- createdAt: messages.createdAt,
47
- })
48
- .from(messages)
49
- .where(eq(messages.id, messageId))
50
- .get();
51
- if (!message || message.role !== "user") return;
52
-
53
- const userMessage = extractTextFromStoredMessageContent(
54
- message.content,
55
- ).trim();
56
- if (userMessage.length === 0) return;
57
- const clarificationReply = looksLikeClarificationReply(userMessage);
58
- if (!clarificationReply) return;
59
-
60
- const pending = listPendingConflictDetails(scopeId, 25);
61
-
62
- // Dismiss non-actionable conflicts (kind or statement policy)
63
- const conflictableKinds = config.memory.conflicts.conflictableKinds;
64
- for (const conflict of pending) {
65
- const kindEligible = isConflictKindPairEligible(
66
- conflict.existingKind,
67
- conflict.candidateKind,
68
- { conflictableKinds },
69
- );
70
- if (
71
- !kindEligible ||
72
- !isStatementConflictEligible(
73
- conflict.existingKind,
74
- conflict.existingStatement,
75
- { conflictableKinds },
76
- ) ||
77
- !isStatementConflictEligible(
78
- conflict.candidateKind,
79
- conflict.candidateStatement,
80
- { conflictableKinds },
81
- )
82
- ) {
83
- resolveConflict(conflict.id, {
84
- status: "dismissed",
85
- resolutionNote: "Dismissed by conflict policy (transient/non-durable).",
86
- });
87
- } else if (
88
- !isConflictUserEvidenced(
89
- conflict.existingVerificationState,
90
- conflict.candidateVerificationState,
91
- )
92
- ) {
93
- resolveConflict(conflict.id, {
94
- status: "dismissed",
95
- resolutionNote:
96
- "Dismissed by conflict policy (no user-evidenced provenance).",
97
- });
98
- } else if (
99
- !areStatementsCoherent(
100
- conflict.existingStatement,
101
- conflict.candidateStatement,
102
- )
103
- ) {
104
- resolveConflict(conflict.id, {
105
- status: "dismissed",
106
- resolutionNote:
107
- "Dismissed by conflict policy (incoherent — zero statement overlap).",
108
- });
109
- }
110
- }
111
-
112
- // Re-fetch after dismissal
113
- const actionablePending = listPendingConflictDetails(scopeId, 25);
114
- const eligible = actionablePending.filter(
115
- (conflict) => conflict.createdAt <= message.createdAt,
116
- );
117
- if (eligible.length === 0) return;
118
- const candidates = eligible.filter((conflict) => {
119
- const relevance = computeConflictRelevance(userMessage, conflict);
120
- return shouldAttemptConflictResolution({
121
- clarificationReply,
122
- relevance,
123
- });
124
- });
125
- if (candidates.length === 0) return;
126
-
127
- let resolvedCount = 0;
128
- for (const conflict of candidates) {
129
- const resolution = await resolveConflictClarification(
130
- {
131
- existingStatement: conflict.existingStatement,
132
- candidateStatement: conflict.candidateStatement,
133
- userMessage,
134
- },
135
- { timeoutMs: config.memory.conflicts.resolverLlmTimeoutMs },
136
- );
137
- if (resolution.resolution === "still_unclear") continue;
138
- const resolved = applyConflictResolution({
139
- conflictId: conflict.id,
140
- resolution: resolution.resolution,
141
- mergedStatement:
142
- resolution.resolution === "merge" ? resolution.resolvedStatement : null,
143
- resolutionNote: `Background message resolver (${resolution.strategy}): ${resolution.explanation}`,
144
- });
145
- if (resolved) resolvedCount += 1;
146
- }
147
-
148
- log.debug(
149
- {
150
- messageId,
151
- scopeId,
152
- pendingConflicts: pending.length,
153
- eligibleConflicts: eligible.length,
154
- candidateConflicts: candidates.length,
155
- resolvedConflicts: resolvedCount,
156
- },
157
- "Processed pending conflict resolution job",
158
- );
159
- }
160
-
161
- export function cleanupResolvedConflictsJob(
162
- job: MemoryJob,
163
- config: AssistantConfig,
164
- ): void {
165
- const db = getDb();
166
- const retentionMs =
167
- asPositiveMs(job.payload.retentionMs) ??
168
- config.memory.cleanup.resolvedConflictRetentionMs;
169
- const cutoff = Date.now() - retentionMs;
170
- const stale = db
171
- .select({ id: memoryItemConflicts.id })
172
- .from(memoryItemConflicts)
173
- .where(
174
- and(
175
- ne(memoryItemConflicts.status, "pending_clarification"),
176
- lt(memoryItemConflicts.resolvedAt, cutoff),
177
- ),
178
- )
179
- .orderBy(asc(memoryItemConflicts.resolvedAt), asc(memoryItemConflicts.id))
180
- .limit(CLEANUP_BATCH_LIMIT)
181
- .all();
182
- if (stale.length === 0) return;
183
-
184
- const ids = stale.map((row) => row.id);
185
- db.delete(memoryItemConflicts)
186
- .where(inArray(memoryItemConflicts.id, ids))
187
- .run();
188
- if (stale.length === CLEANUP_BATCH_LIMIT) {
189
- enqueueMemoryJob("cleanup_resolved_conflicts", { retentionMs });
190
- }
191
-
192
- log.debug(
193
- {
194
- removedConflicts: stale.length,
195
- retentionMs,
196
- cutoff,
197
- },
198
- "Cleaned up resolved memory conflicts",
199
- );
200
- }
@@ -1,195 +0,0 @@
1
- import { and, desc, eq, inArray, isNull } from "drizzle-orm";
2
-
3
- import { getConfig } from "../config/loader.js";
4
- import { estimateTextTokens } from "../context/token-estimator.js";
5
- import { getDb } from "./db.js";
6
- import { memoryItems } from "./schema.js";
7
-
8
- const PROFILE_KIND_ALLOWLIST = [
9
- "profile",
10
- "preference",
11
- "constraint",
12
- "instruction",
13
- "style",
14
- ] as const;
15
-
16
- const TRUST_RANK: Record<string, number> = {
17
- user_confirmed: 3,
18
- user_reported: 2,
19
- assistant_inferred: 1,
20
- };
21
-
22
- export interface CompileProfileOptions {
23
- scopeId?: string;
24
- maxInjectTokensOverride?: number;
25
- /** When true and scopeId is not 'default', query both the given scope and 'default'. */
26
- includeDefaultFallback?: boolean;
27
- }
28
-
29
- export interface CompiledProfile {
30
- text: string;
31
- sourceCount: number;
32
- selectedCount: number;
33
- budgetTokens: number;
34
- tokenEstimate: number;
35
- }
36
-
37
- interface ProfileCandidate {
38
- kind: string;
39
- subject: string;
40
- statement: string;
41
- verificationState: string;
42
- confidence: number;
43
- importance: number | null;
44
- lastSeenAt: number;
45
- firstSeenAt: number;
46
- scopeId: string;
47
- }
48
-
49
- export function compileDynamicProfile(
50
- options?: CompileProfileOptions,
51
- ): CompiledProfile {
52
- const config = getConfig();
53
- const profileConfig = config.memory.profile;
54
- const scopeId = options?.scopeId ?? "default";
55
- const budgetTokens = Math.max(
56
- 0,
57
- Math.floor(
58
- options?.maxInjectTokensOverride ?? profileConfig.maxInjectTokens,
59
- ),
60
- );
61
- if (!profileConfig.enabled || budgetTokens <= 0) {
62
- return {
63
- text: "",
64
- sourceCount: 0,
65
- selectedCount: 0,
66
- budgetTokens,
67
- tokenEstimate: 0,
68
- };
69
- }
70
-
71
- const db = getDb();
72
- const shouldFallback =
73
- options?.includeDefaultFallback === true && scopeId !== "default";
74
- const scopeFilter = shouldFallback
75
- ? inArray(memoryItems.scopeId, [scopeId, "default"])
76
- : eq(memoryItems.scopeId, scopeId);
77
- const rows = db
78
- .select({
79
- kind: memoryItems.kind,
80
- subject: memoryItems.subject,
81
- statement: memoryItems.statement,
82
- verificationState: memoryItems.verificationState,
83
- confidence: memoryItems.confidence,
84
- importance: memoryItems.importance,
85
- lastSeenAt: memoryItems.lastSeenAt,
86
- firstSeenAt: memoryItems.firstSeenAt,
87
- scopeId: memoryItems.scopeId,
88
- })
89
- .from(memoryItems)
90
- .where(
91
- and(
92
- scopeFilter,
93
- eq(memoryItems.status, "active"),
94
- isNull(memoryItems.invalidAt),
95
- inArray(memoryItems.kind, [...PROFILE_KIND_ALLOWLIST]),
96
- ),
97
- )
98
- .orderBy(desc(memoryItems.lastSeenAt))
99
- .all();
100
-
101
- const nowMs = Date.now();
102
- const trusted = rows
103
- .filter((row) => TRUST_RANK[row.verificationState] !== undefined)
104
- .sort((a, b) =>
105
- compareProfileCandidates(
106
- a,
107
- b,
108
- nowMs,
109
- shouldFallback ? scopeId : undefined,
110
- ),
111
- );
112
-
113
- const selectedLines: string[] = [];
114
- const seenKeys = new Set<string>();
115
- for (const candidate of trusted) {
116
- const subject = normalizeWhitespace(candidate.subject, 80);
117
- const statement = normalizeWhitespace(candidate.statement, 220);
118
- if (!subject || !statement) continue;
119
- const dedupeKey = `${candidate.kind}|${subject.toLowerCase()}`;
120
- if (seenKeys.has(dedupeKey)) continue;
121
-
122
- const line = `- ${subject}: ${statement}`;
123
- const tentative = renderProfileText([...selectedLines, line]);
124
- if (estimateTextTokens(tentative) > budgetTokens) continue;
125
- seenKeys.add(dedupeKey);
126
- selectedLines.push(line);
127
- }
128
-
129
- const text = renderProfileText(selectedLines);
130
- const tokenEstimate = text ? estimateTextTokens(text) : 0;
131
- return {
132
- text,
133
- sourceCount: trusted.length,
134
- selectedCount: selectedLines.length,
135
- budgetTokens,
136
- tokenEstimate,
137
- };
138
- }
139
-
140
- /** Half-life for recency decay — items seen this many ms ago score ~0.5. (30 days) */
141
- const RECENCY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1000;
142
-
143
- function recencyScore(lastSeenAt: number, nowMs: number): number {
144
- const ageMs = Math.max(0, nowMs - lastSeenAt);
145
- return Math.pow(0.5, ageMs / RECENCY_HALF_LIFE_MS);
146
- }
147
-
148
- function candidateRankScore(c: ProfileCandidate, nowMs: number): number {
149
- const importance = c.importance ?? 0.5;
150
- const recency = recencyScore(c.lastSeenAt, nowMs);
151
- // Use weighted additive formula: importance dominates, recency is a minor boost
152
- // This preserves high-importance items even when they haven't been seen recently
153
- return importance * 0.7 + recency * 0.3;
154
- }
155
-
156
- function compareProfileCandidates(
157
- left: ProfileCandidate,
158
- right: ProfileCandidate,
159
- nowMs: number,
160
- /** When set, entries matching this scope sort before 'default' entries. */
161
- preferredScopeId?: string,
162
- ): number {
163
- // Scoped entries beat default fallback entries so local overrides win deduplication
164
- if (preferredScopeId !== undefined) {
165
- const leftIsPreferred = left.scopeId === preferredScopeId;
166
- const rightIsPreferred = right.scopeId === preferredScopeId;
167
- if (leftIsPreferred && !rightIsPreferred) return -1;
168
- if (!leftIsPreferred && rightIsPreferred) return 1;
169
- }
170
-
171
- const trustDelta =
172
- (TRUST_RANK[right.verificationState] ?? 0) -
173
- (TRUST_RANK[left.verificationState] ?? 0);
174
- if (trustDelta !== 0) return trustDelta;
175
-
176
- const scoreDelta =
177
- candidateRankScore(right, nowMs) - candidateRankScore(left, nowMs);
178
- if (scoreDelta !== 0) return scoreDelta;
179
-
180
- const confidenceDelta = right.confidence - left.confidence;
181
- if (confidenceDelta !== 0) return confidenceDelta;
182
-
183
- return right.firstSeenAt - left.firstSeenAt;
184
- }
185
-
186
- function normalizeWhitespace(input: string, maxLength: number): string {
187
- return input.replace(/\s+/g, " ").trim().slice(0, maxLength);
188
- }
189
-
190
- function renderProfileText(lines: string[]): string {
191
- if (lines.length === 0) return "";
192
- return ["<dynamic-user-profile>", ...lines, "</dynamic-user-profile>"].join(
193
- "\n",
194
- );
195
- }
@@ -1,117 +0,0 @@
1
- import { createHash } from "crypto";
2
-
3
- import type {
4
- MemoryRecallOptions,
5
- MemoryRecallResult,
6
- } from "./search/types.js";
7
-
8
- /**
9
- * In-memory cache for memory recall results.
10
- *
11
- * The full retrieval pipeline (FTS5 + Qdrant + entity graph + RRF merge) is
12
- * expensive. When the same query is issued multiple turns in a row (common
13
- * when the conversation context hasn't changed), we can serve the cached
14
- * result instantly.
15
- *
16
- * Invalidation: a monotonic version counter is bumped whenever new memory
17
- * is indexed (segments, items, embeddings). Cache entries are only valid
18
- * when their version matches the current global version.
19
- */
20
-
21
- interface CacheEntry {
22
- version: number;
23
- createdAt: number;
24
- result: MemoryRecallResult;
25
- }
26
-
27
- const MAX_ENTRIES = 32;
28
- const TTL_MS = 60_000; // 60 seconds
29
-
30
- let _version = 0;
31
- const _cache = new Map<string, CacheEntry>();
32
-
33
- /** Bump the global memory version, invalidating all cached recall results. */
34
- export function bumpMemoryVersion(): void {
35
- _version++;
36
- }
37
-
38
- /** Return the current memory version (for snapshot-based staleness checks). */
39
- export function getMemoryVersion(): number {
40
- return _version;
41
- }
42
-
43
- /** Build a deterministic cache key from the recall inputs. */
44
- function buildCacheKey(
45
- query: string,
46
- conversationId: string,
47
- options?: MemoryRecallOptions,
48
- configFingerprint?: string,
49
- ): string {
50
- const parts = [
51
- query,
52
- conversationId,
53
- options?.scopeId ?? "",
54
- options?.scopePolicyOverride
55
- ? `${options.scopePolicyOverride.scopeId}:${options.scopePolicyOverride.fallbackToDefault}`
56
- : "",
57
- options?.excludeMessageIds
58
- ? [...options.excludeMessageIds].sort().join(",")
59
- : "",
60
- options?.maxInjectTokensOverride != null
61
- ? String(options.maxInjectTokensOverride)
62
- : "",
63
- configFingerprint ?? "",
64
- ];
65
- return createHash("sha256").update(parts.join("\0")).digest("hex");
66
- }
67
-
68
- /** Look up a cached recall result. Returns undefined on miss or stale entry. */
69
- export function getCachedRecall(
70
- query: string,
71
- conversationId: string,
72
- options?: MemoryRecallOptions,
73
- configFingerprint?: string,
74
- ): MemoryRecallResult | undefined {
75
- const key = buildCacheKey(query, conversationId, options, configFingerprint);
76
- const entry = _cache.get(key);
77
- if (!entry) return undefined;
78
- if (entry.version !== _version || Date.now() - entry.createdAt > TTL_MS) {
79
- _cache.delete(key);
80
- return undefined;
81
- }
82
- // Move to end of Map iteration order so it's treated as most-recently-used
83
- _cache.delete(key);
84
- _cache.set(key, entry);
85
- return entry.result;
86
- }
87
-
88
- /**
89
- * Store a recall result in the cache. Evicts least-recently-used entries when full.
90
- *
91
- * When `snapshotVersion` is provided, the entry is only stored if the
92
- * snapshot still matches the current global version — this prevents a
93
- * stale result from being cached under a version that was bumped while
94
- * the retrieval pipeline was in flight.
95
- */
96
- export function setCachedRecall(
97
- query: string,
98
- conversationId: string,
99
- options: MemoryRecallOptions | undefined,
100
- result: MemoryRecallResult,
101
- snapshotVersion?: number,
102
- configFingerprint?: string,
103
- ): void {
104
- // If a snapshot version was provided, only cache when it still matches
105
- // the current version — otherwise the result may be stale.
106
- if (snapshotVersion !== undefined && snapshotVersion !== _version) return;
107
-
108
- const key = buildCacheKey(query, conversationId, options, configFingerprint);
109
-
110
- // Evict oldest entries if at capacity
111
- if (_cache.size >= MAX_ENTRIES && !_cache.has(key)) {
112
- const oldest = _cache.keys().next().value;
113
- if (oldest !== undefined) _cache.delete(oldest);
114
- }
115
-
116
- _cache.set(key, { version: _version, createdAt: Date.now(), result });
117
- }