@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,535 +0,0 @@
1
- import { eq, sql } from "drizzle-orm";
2
-
3
- import type { MemoryEntityConfig } from "../config/types.js";
4
- import {
5
- createTimeout,
6
- extractToolUse,
7
- getConfiguredProvider,
8
- userMessage,
9
- } from "../providers/provider-send-message.js";
10
- import { getLogger } from "../util/logger.js";
11
- import { truncate } from "../util/truncate.js";
12
- import { getDb, rawAll } from "./db.js";
13
- import {
14
- memoryEntities,
15
- memoryEntityRelations,
16
- memoryItemEntities,
17
- } from "./schema.js";
18
-
19
- const log = getLogger("memory-entity-extractor");
20
-
21
- const ENTITY_EXTRACTION_TIMEOUT_MS = 15_000;
22
-
23
- export type EntityType =
24
- | "person"
25
- | "project"
26
- | "tool"
27
- | "company"
28
- | "concept"
29
- | "location"
30
- | "organization";
31
-
32
- export type EntityRelationType =
33
- | "works_on"
34
- | "uses"
35
- | "owns"
36
- | "member_of"
37
- | "located_in"
38
- | "depends_on"
39
- | "collaborates_with"
40
- | "reports_to"
41
- | "related_to";
42
-
43
- const VALID_ENTITY_TYPES = new Set<string>([
44
- "person",
45
- "project",
46
- "tool",
47
- "company",
48
- "concept",
49
- "location",
50
- "organization",
51
- ]);
52
-
53
- const VALID_RELATION_TYPES = new Set<string>([
54
- "works_on",
55
- "uses",
56
- "owns",
57
- "member_of",
58
- "located_in",
59
- "depends_on",
60
- "collaborates_with",
61
- "reports_to",
62
- "related_to",
63
- ]);
64
-
65
- export interface ExtractedEntity {
66
- name: string;
67
- type: EntityType;
68
- aliases: string[];
69
- }
70
-
71
- export interface ExtractedEntityRelation {
72
- sourceEntityName: string;
73
- targetEntityName: string;
74
- relation: EntityRelationType;
75
- evidence: string | null;
76
- }
77
-
78
- export interface ExtractedEntityGraph {
79
- entities: ExtractedEntity[];
80
- relations: ExtractedEntityRelation[];
81
- }
82
-
83
- export interface UpsertEntityRelationInput {
84
- sourceEntityId: string;
85
- targetEntityId: string;
86
- relation: EntityRelationType;
87
- evidence?: string | null;
88
- seenAt?: number;
89
- }
90
-
91
- interface LLMExtractedEntity {
92
- name: string;
93
- type: string;
94
- aliases: string[];
95
- }
96
-
97
- interface LLMExtractedRelation {
98
- sourceEntityName: string;
99
- targetEntityName: string;
100
- relation: string;
101
- evidence?: string;
102
- }
103
-
104
- const ENTITY_EXTRACTION_SYSTEM_PROMPT = `You are an entity extraction system. Given text from a conversation, extract named entities that are worth tracking across conversations.
105
-
106
- Extract entities in these categories:
107
- - person: People mentioned by name (users, colleagues, contacts)
108
- - project: Named projects, repositories, products, apps
109
- - tool: Software tools, libraries, frameworks, languages
110
- - company: Companies, organizations with commercial identity
111
- - concept: Technical concepts, methodologies, design patterns
112
- - location: Cities, offices, regions relevant to the user
113
- - organization: Non-commercial orgs, teams, groups, communities
114
-
115
- If relation extraction is enabled, also extract directional entity relations using:
116
- - works_on, uses, owns, member_of, located_in, depends_on, collaborates_with, reports_to, related_to
117
-
118
- For each entity, provide:
119
- - name: The canonical name (proper casing, full name preferred)
120
- - type: One of the categories above
121
- - aliases: Array of alternate names, abbreviations, or nicknames (empty array if none)
122
-
123
- If relation extraction is enabled, for each relation provide:
124
- - sourceEntityName: canonical source entity name
125
- - targetEntityName: canonical target entity name
126
- - relation: one of the allowed relation types
127
- - evidence: short evidence phrase from the text (optional)
128
-
129
- Rules:
130
- - Only extract concrete, named entities. Skip generic terms like "the project" or "that tool".
131
- - Prefer the most specific and complete name as the canonical name.
132
- - Include common abbreviations and nicknames as aliases.
133
- - Do NOT extract the assistant itself or generic conversation participants.
134
- - If there are no extractable entities, return an empty entities array.
135
- - Only emit relations that are explicitly or strongly implied by the text.`;
136
-
137
- export async function extractEntitiesWithLLM(
138
- text: string,
139
- entityConfig: MemoryEntityConfig,
140
- ): Promise<ExtractedEntityGraph> {
141
- const provider = getConfiguredProvider();
142
- if (!provider) {
143
- log.debug("Configured provider unavailable for entity extraction");
144
- return { entities: [], relations: [] };
145
- }
146
-
147
- const extractRelations = entityConfig.extractRelations?.enabled ?? false;
148
-
149
- try {
150
- const { signal, cleanup } = createTimeout(ENTITY_EXTRACTION_TIMEOUT_MS);
151
- try {
152
- const response = await provider.sendMessage(
153
- [userMessage(text)],
154
- [
155
- {
156
- name: "store_entities",
157
- description: "Store extracted entities from the text",
158
- input_schema: buildToolInputSchema(extractRelations),
159
- },
160
- ],
161
- ENTITY_EXTRACTION_SYSTEM_PROMPT,
162
- {
163
- config: {
164
- modelIntent: entityConfig.modelIntent,
165
- max_tokens: 1024,
166
- tool_choice: { type: "tool" as const, name: "store_entities" },
167
- },
168
- signal,
169
- },
170
- );
171
- cleanup();
172
-
173
- const toolBlock = extractToolUse(response);
174
- if (!toolBlock) {
175
- log.warn("No tool_use block in entity extraction response");
176
- return { entities: [], relations: [] };
177
- }
178
-
179
- const input = toolBlock.input as {
180
- entities?: LLMExtractedEntity[];
181
- relations?: LLMExtractedRelation[];
182
- };
183
- if (!Array.isArray(input.entities)) {
184
- log.warn("Invalid entities in entity extraction response");
185
- return { entities: [], relations: [] };
186
- }
187
-
188
- const entities = parseExtractedEntities(input.entities);
189
- const relations = extractRelations
190
- ? parseExtractedRelations(input.relations)
191
- : [];
192
-
193
- return { entities, relations };
194
- } finally {
195
- cleanup();
196
- }
197
- } catch (err) {
198
- const message = err instanceof Error ? err.message : String(err);
199
- log.warn({ err: message }, "Entity extraction LLM call failed");
200
- return { entities: [], relations: [] };
201
- }
202
- }
203
-
204
- /**
205
- * Resolve an extracted entity against existing entities in the database.
206
- * Returns the existing entity ID if a match is found, or null if no match.
207
- */
208
- export function resolveEntity(entity: ExtractedEntity): string | null {
209
- const candidates = findEntityCandidates(entity.name);
210
- if (candidates.length > 0) {
211
- const sameType = candidates.find(
212
- (candidate) => candidate.type === entity.type,
213
- );
214
- return sameType?.id ?? candidates[0].id;
215
- }
216
-
217
- for (const alias of entity.aliases) {
218
- const aliasCandidates = findEntityCandidates(alias);
219
- if (aliasCandidates.length > 0) {
220
- const sameType = aliasCandidates.find(
221
- (candidate) => candidate.type === entity.type,
222
- );
223
- return sameType?.id ?? aliasCandidates[0].id;
224
- }
225
- }
226
- return null;
227
- }
228
-
229
- /**
230
- * Resolve an entity by canonical name or alias.
231
- * Prefers exact canonical name matches over alias-only matches so that
232
- * relations are attached to the correct node.
233
- */
234
- export function resolveEntityName(entityName: string): string | null {
235
- const candidates = findEntityCandidates(entityName);
236
- if (candidates.length === 0) return null;
237
- const nameLower = entityName.trim().toLowerCase();
238
- const exactNameMatch = candidates.find(
239
- (c) => c.name.toLowerCase() === nameLower,
240
- );
241
- return exactNameMatch?.id ?? candidates[0].id;
242
- }
243
-
244
- /**
245
- * Upsert an entity into the database: resolve against existing entities,
246
- * update if found, or insert a new one.
247
- * Returns the entity ID.
248
- */
249
- export function upsertEntity(entity: ExtractedEntity): string {
250
- const db = getDb();
251
- const now = Date.now();
252
- const existingId = resolveEntity(entity);
253
-
254
- if (existingId) {
255
- const existing = db
256
- .select()
257
- .from(memoryEntities)
258
- .where(eq(memoryEntities.id, existingId))
259
- .get();
260
-
261
- if (existing) {
262
- const existingAliases: string[] = existing.aliases
263
- ? (JSON.parse(existing.aliases) as string[])
264
- : [];
265
- const mergedAliases = mergeAliases(
266
- existingAliases,
267
- entity.aliases,
268
- existing.name,
269
- );
270
-
271
- db.update(memoryEntities)
272
- .set({
273
- lastSeenAt: now,
274
- mentionCount: sql`${memoryEntities.mentionCount} + 1`,
275
- aliases:
276
- mergedAliases.length > 0 ? JSON.stringify(mergedAliases) : null,
277
- })
278
- .where(eq(memoryEntities.id, existingId))
279
- .run();
280
- }
281
-
282
- return existingId;
283
- }
284
-
285
- const id = crypto.randomUUID();
286
- db.insert(memoryEntities)
287
- .values({
288
- id,
289
- name: entity.name,
290
- type: entity.type,
291
- aliases:
292
- entity.aliases.length > 0 ? JSON.stringify(entity.aliases) : null,
293
- description: null,
294
- firstSeenAt: now,
295
- lastSeenAt: now,
296
- mentionCount: 1,
297
- })
298
- .run();
299
-
300
- return id;
301
- }
302
-
303
- /**
304
- * Upsert an entity relation edge using (source, target, relation) as a stable uniqueness key.
305
- */
306
- export function upsertEntityRelation(input: UpsertEntityRelationInput): void {
307
- if (input.sourceEntityId === input.targetEntityId) return;
308
-
309
- const db = getDb();
310
- const seenAt = input.seenAt ?? Date.now();
311
- const normalizedEvidence = normalizeEvidence(input.evidence);
312
-
313
- db.insert(memoryEntityRelations)
314
- .values({
315
- id: crypto.randomUUID(),
316
- sourceEntityId: input.sourceEntityId,
317
- targetEntityId: input.targetEntityId,
318
- relation: input.relation,
319
- evidence: normalizedEvidence,
320
- firstSeenAt: seenAt,
321
- lastSeenAt: seenAt,
322
- })
323
- .onConflictDoUpdate({
324
- target: [
325
- memoryEntityRelations.sourceEntityId,
326
- memoryEntityRelations.targetEntityId,
327
- memoryEntityRelations.relation,
328
- ],
329
- set:
330
- normalizedEvidence === undefined
331
- ? {
332
- firstSeenAt: sql`MIN(${memoryEntityRelations.firstSeenAt}, ${seenAt})`,
333
- lastSeenAt: sql`MAX(${memoryEntityRelations.lastSeenAt}, ${seenAt})`,
334
- }
335
- : {
336
- firstSeenAt: sql`MIN(${memoryEntityRelations.firstSeenAt}, ${seenAt})`,
337
- lastSeenAt: sql`MAX(${memoryEntityRelations.lastSeenAt}, ${seenAt})`,
338
- evidence: normalizedEvidence,
339
- },
340
- })
341
- .run();
342
- }
343
-
344
- /**
345
- * Link a memory item to an entity via the join table.
346
- */
347
- export function linkMemoryItemToEntity(
348
- memoryItemId: string,
349
- entityId: string,
350
- ): void {
351
- const db = getDb();
352
- db.insert(memoryItemEntities)
353
- .values({
354
- memoryItemId,
355
- entityId,
356
- })
357
- .onConflictDoNothing()
358
- .run();
359
- }
360
-
361
- type ToolInputSchema = Record<string, unknown> & {
362
- type: "object";
363
- properties: Record<string, unknown>;
364
- required: string[];
365
- };
366
-
367
- function buildToolInputSchema(includeRelations: boolean): ToolInputSchema {
368
- const properties: Record<string, unknown> = {
369
- entities: {
370
- type: "array",
371
- items: {
372
- type: "object",
373
- properties: {
374
- name: {
375
- type: "string",
376
- description: "Canonical name of the entity",
377
- },
378
- type: {
379
- type: "string",
380
- enum: [...VALID_ENTITY_TYPES],
381
- description: "Category of the entity",
382
- },
383
- aliases: {
384
- type: "array",
385
- items: { type: "string" },
386
- description: "Alternate names or abbreviations",
387
- },
388
- },
389
- required: ["name", "type", "aliases"],
390
- },
391
- },
392
- };
393
-
394
- const required: string[] = ["entities"];
395
-
396
- if (includeRelations) {
397
- properties.relations = {
398
- type: "array",
399
- items: {
400
- type: "object",
401
- properties: {
402
- sourceEntityName: { type: "string" },
403
- targetEntityName: { type: "string" },
404
- relation: {
405
- type: "string",
406
- enum: [...VALID_RELATION_TYPES],
407
- },
408
- evidence: { type: "string" },
409
- },
410
- required: ["sourceEntityName", "targetEntityName", "relation"],
411
- },
412
- };
413
- required.push("relations");
414
- }
415
-
416
- return {
417
- type: "object",
418
- properties,
419
- required,
420
- };
421
- }
422
-
423
- function parseExtractedEntities(
424
- rawEntities: LLMExtractedEntity[],
425
- ): ExtractedEntity[] {
426
- const entities: ExtractedEntity[] = [];
427
- const seen = new Set<string>();
428
- for (const raw of rawEntities) {
429
- if (!VALID_ENTITY_TYPES.has(raw.type)) continue;
430
- const name = normalizeEntityName(raw.name);
431
- if (!name) continue;
432
-
433
- const aliases = Array.isArray(raw.aliases)
434
- ? dedupeAliasList(raw.aliases, name)
435
- : [];
436
- const dedupeKey = `${name.toLowerCase()}|${raw.type}`;
437
- if (seen.has(dedupeKey)) continue;
438
- seen.add(dedupeKey);
439
- entities.push({
440
- name,
441
- type: raw.type as EntityType,
442
- aliases,
443
- });
444
- }
445
- return entities;
446
- }
447
-
448
- function parseExtractedRelations(
449
- rawRelations: LLMExtractedRelation[] | undefined,
450
- ): ExtractedEntityRelation[] {
451
- if (!Array.isArray(rawRelations)) return [];
452
- const relations: ExtractedEntityRelation[] = [];
453
- const seen = new Set<string>();
454
- for (const raw of rawRelations) {
455
- if (!VALID_RELATION_TYPES.has(raw.relation)) continue;
456
- const sourceEntityName = normalizeEntityName(raw.sourceEntityName);
457
- const targetEntityName = normalizeEntityName(raw.targetEntityName);
458
- if (!sourceEntityName || !targetEntityName) continue;
459
- if (sourceEntityName.toLowerCase() === targetEntityName.toLowerCase())
460
- continue;
461
- const relation = raw.relation as EntityRelationType;
462
- const dedupeKey = `${sourceEntityName.toLowerCase()}|${targetEntityName.toLowerCase()}|${relation}`;
463
- if (seen.has(dedupeKey)) continue;
464
- seen.add(dedupeKey);
465
- relations.push({
466
- sourceEntityName,
467
- targetEntityName,
468
- relation,
469
- evidence: normalizeEvidence(raw.evidence),
470
- });
471
- }
472
- return relations;
473
- }
474
-
475
- function findEntityCandidates(
476
- nameOrAlias: string,
477
- ): Array<typeof memoryEntities.$inferSelect> {
478
- const normalized = normalizeEntityName(nameOrAlias);
479
- if (!normalized) return [];
480
- const nameLower = normalized.toLowerCase();
481
-
482
- return rawAll<typeof memoryEntities.$inferSelect>(
483
- `
484
- SELECT DISTINCT me.* FROM memory_entities me
485
- WHERE LOWER(me.name) = ?
486
- UNION
487
- SELECT DISTINCT me.* FROM memory_entities me, json_each(me.aliases) je
488
- WHERE me.aliases IS NOT NULL AND LOWER(je.value) = ?
489
- `,
490
- nameLower,
491
- nameLower,
492
- );
493
- }
494
-
495
- /**
496
- * Merge alias lists, deduplicating and excluding the canonical name.
497
- */
498
- function mergeAliases(
499
- existing: string[],
500
- incoming: string[],
501
- canonicalName: string,
502
- ): string[] {
503
- const seen = new Set<string>();
504
- const canonicalLower = canonicalName.toLowerCase();
505
- const merged: string[] = [];
506
- for (const alias of [...existing, ...incoming]) {
507
- const normalizedAlias = normalizeEntityName(alias);
508
- if (!normalizedAlias) continue;
509
- const lower = normalizedAlias.toLowerCase();
510
- if (lower === canonicalLower) continue;
511
- if (seen.has(lower)) continue;
512
- seen.add(lower);
513
- merged.push(normalizedAlias);
514
- }
515
- return merged;
516
- }
517
-
518
- function dedupeAliasList(
519
- rawAliases: string[],
520
- canonicalName: string,
521
- ): string[] {
522
- return mergeAliases([], rawAliases, canonicalName);
523
- }
524
-
525
- function normalizeEntityName(value: string | null | undefined): string | null {
526
- if (!value) return null;
527
- const normalized = truncate(String(value).trim(), 200, "");
528
- return normalized.length > 0 ? normalized : null;
529
- }
530
-
531
- function normalizeEvidence(value: string | null | undefined): string | null {
532
- if (!value) return null;
533
- const normalized = truncate(String(value).trim(), 500, "");
534
- return normalized.length > 0 ? normalized : null;
535
- }
@@ -1,47 +0,0 @@
1
- import { estimateTextTokens } from "../context/token-estimator.js";
2
- import { buildInjectedText } from "./search/formatting.js";
3
- import { markItemUsage, trimToTokenBudget } from "./search/ranking.js";
4
- import type { Candidate } from "./search/types.js";
5
-
6
- export interface FormatRecallTextOptions {
7
- /** Injection format: 'markdown' or 'structured_v1'. */
8
- format: string;
9
- /** Maximum token budget for the formatted output. */
10
- maxTokens: number;
11
- }
12
-
13
- export interface FormatRecallTextResult {
14
- /** The formatted text ready for injection. */
15
- text: string;
16
- /** Candidates that fit within the token budget. */
17
- selected: Candidate[];
18
- /** Token count of the final injected text. */
19
- tokenCount: number;
20
- }
21
-
22
- /**
23
- * Format scored recall candidates into injectable text.
24
- *
25
- * Trims candidates to the token budget, groups by section with temporal
26
- * grounding, applies "Lost in the Middle" ordering, and marks item usage.
27
- *
28
- * Extracted from `formatRecallResult()` in `retriever.ts` so both the
29
- * auto-injection path and the on-demand memory_recall tool can reuse it.
30
- */
31
- export function formatRecallText(
32
- candidates: Candidate[],
33
- opts: FormatRecallTextOptions,
34
- ): FormatRecallTextResult {
35
- const { format, maxTokens } = opts;
36
-
37
- const selected = trimToTokenBudget(candidates, maxTokens, format);
38
- markItemUsage(selected);
39
-
40
- const text = buildInjectedText(selected, format);
41
-
42
- return {
43
- text,
44
- selected,
45
- tokenCount: estimateTextTokens(text),
46
- };
47
- }