@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,361 +0,0 @@
1
- import { mkdtempSync, rmSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import {
5
- afterAll,
6
- beforeAll,
7
- beforeEach,
8
- describe,
9
- expect,
10
- mock,
11
- test,
12
- } from "bun:test";
13
-
14
- import { eq } from "drizzle-orm";
15
-
16
- const testDir = mkdtempSync(join(tmpdir(), "contradiction-checker-test-"));
17
-
18
- let nextRelationship = "ambiguous_contradiction";
19
- let nextExplanation = "Statements likely conflict but need confirmation.";
20
- let classifyCallCount = 0;
21
-
22
- const classifyRelationshipMock = mock(async () => {
23
- classifyCallCount += 1;
24
- return {
25
- content: [
26
- {
27
- type: "tool_use" as const,
28
- id: "test-tool-use-id",
29
- name: "classify_relationship",
30
- input: {
31
- relationship: nextRelationship,
32
- explanation: nextExplanation,
33
- },
34
- },
35
- ],
36
- model: "claude-haiku-4-5-20251001",
37
- stopReason: "tool_use",
38
- usage: { inputTokens: 0, outputTokens: 0 },
39
- };
40
- });
41
-
42
- mock.module("../providers/provider-send-message.js", () => ({
43
- getConfiguredProvider: () => ({
44
- sendMessage: classifyRelationshipMock,
45
- }),
46
- createTimeout: (ms: number) => {
47
- const controller = new AbortController();
48
- const timer = setTimeout(() => controller.abort(), ms);
49
- return {
50
- signal: controller.signal,
51
- cleanup: () => clearTimeout(timer),
52
- };
53
- },
54
- extractToolUse: (response: { content: Array<{ type: string }> }) => {
55
- return response.content.find(
56
- (b: { type: string }) => b.type === "tool_use",
57
- );
58
- },
59
- userMessage: (text: string) => ({
60
- role: "user",
61
- content: [{ type: "text", text }],
62
- }),
63
- }));
64
-
65
- mock.module("../util/platform.js", () => ({
66
- getDataDir: () => testDir,
67
- isMacOS: () => process.platform === "darwin",
68
- isLinux: () => process.platform === "linux",
69
- isWindows: () => process.platform === "win32",
70
- getPidPath: () => join(testDir, "test.pid"),
71
- getDbPath: () => join(testDir, "test.db"),
72
- getLogPath: () => join(testDir, "test.log"),
73
- ensureDataDir: () => {},
74
- }));
75
-
76
- mock.module("../util/logger.js", () => ({
77
- getLogger: () =>
78
- new Proxy({} as Record<string, unknown>, {
79
- get: () => () => {},
80
- }),
81
- }));
82
-
83
- let mockConflictableKinds: string[] = [
84
- "preference",
85
- "profile",
86
- "constraint",
87
- "instruction",
88
- "style",
89
- ];
90
-
91
- mock.module("../config/loader.js", () => ({
92
- getConfig: () => ({
93
- ui: {},
94
-
95
- apiKeys: { anthropic: "test-key" },
96
- memory: {
97
- conflicts: {
98
- conflictableKinds: mockConflictableKinds,
99
- },
100
- },
101
- }),
102
- }));
103
-
104
- import { checkContradictions } from "../memory/contradiction-checker.js";
105
- import { getDb, initializeDb, resetDb } from "../memory/db.js";
106
- import { memoryItemConflicts, memoryItems } from "../memory/schema.js";
107
-
108
- beforeAll(() => {
109
- initializeDb();
110
- });
111
-
112
- beforeEach(() => {
113
- classifyCallCount = 0;
114
- mockConflictableKinds = [
115
- "preference",
116
- "profile",
117
- "constraint",
118
- "instruction",
119
- "style",
120
- ];
121
- const db = getDb();
122
- db.run("DELETE FROM memory_item_conflicts");
123
- db.run("DELETE FROM memory_item_sources");
124
- db.run("DELETE FROM memory_jobs");
125
- db.run("DELETE FROM memory_items");
126
- });
127
-
128
- afterAll(() => {
129
- resetDb();
130
- try {
131
- rmSync(testDir, { recursive: true, force: true });
132
- } catch {
133
- // best effort cleanup
134
- }
135
- });
136
-
137
- function insertMemoryItem(params: {
138
- id: string;
139
- statement: string;
140
- scopeId?: string;
141
- status?: "active" | "pending_clarification";
142
- kind?: string;
143
- }): void {
144
- const now = Date.now();
145
- const db = getDb();
146
- db.insert(memoryItems)
147
- .values({
148
- id: params.id,
149
- kind: params.kind ?? "preference",
150
- subject: "framework preference",
151
- statement: params.statement,
152
- status: params.status ?? "active",
153
- confidence: 0.8,
154
- importance: 0.7,
155
- fingerprint: `fp-${params.id}`,
156
- verificationState: "assistant_inferred",
157
- scopeId: params.scopeId ?? "default",
158
- firstSeenAt: now,
159
- lastSeenAt: now,
160
- })
161
- .run();
162
- }
163
-
164
- describe("checkContradictions", () => {
165
- test("marks candidate pending and writes one conflict row for ambiguous contradictions", async () => {
166
- nextRelationship = "ambiguous_contradiction";
167
- nextExplanation = "Seems contradictory; ask user to choose.";
168
-
169
- insertMemoryItem({
170
- id: "item-existing-ambiguous",
171
- statement: "User prefers React for frontend work.",
172
- scopeId: "workspace-a",
173
- });
174
- insertMemoryItem({
175
- id: "item-candidate-ambiguous",
176
- statement: "User prefers Vue for frontend work.",
177
- scopeId: "workspace-a",
178
- });
179
-
180
- await checkContradictions("item-candidate-ambiguous");
181
-
182
- const db = getDb();
183
- const candidate = db
184
- .select()
185
- .from(memoryItems)
186
- .where(eq(memoryItems.id, "item-candidate-ambiguous"))
187
- .get();
188
- const existing = db
189
- .select()
190
- .from(memoryItems)
191
- .where(eq(memoryItems.id, "item-existing-ambiguous"))
192
- .get();
193
- const conflicts = db.select().from(memoryItemConflicts).all();
194
-
195
- expect(classifyCallCount).toBe(1);
196
- expect(candidate?.status).toBe("pending_clarification");
197
- expect(existing?.invalidAt).toBeNull();
198
- expect(conflicts).toHaveLength(1);
199
- expect(conflicts[0].status).toBe("pending_clarification");
200
- expect(conflicts[0].existingItemId).toBe("item-existing-ambiguous");
201
- expect(conflicts[0].candidateItemId).toBe("item-candidate-ambiguous");
202
- expect(conflicts[0].relationship).toBe("ambiguous_contradiction");
203
- expect(conflicts[0].clarificationQuestion).toContain("Pending conflict:");
204
- expect(conflicts[0].clarificationQuestion).not.toContain(
205
- "I have conflicting notes",
206
- );
207
- expect(conflicts[0].clarificationQuestion).not.toContain(
208
- "Which one is correct?",
209
- );
210
- });
211
-
212
- test("keeps existing contradiction behavior and does not create conflict row", async () => {
213
- nextRelationship = "contradiction";
214
- nextExplanation = "The statements are directly incompatible.";
215
-
216
- insertMemoryItem({
217
- id: "item-existing-contradiction",
218
- statement: "User prefers dark mode.",
219
- });
220
- insertMemoryItem({
221
- id: "item-candidate-contradiction",
222
- statement: "User prefers light mode.",
223
- });
224
-
225
- await checkContradictions("item-candidate-contradiction");
226
-
227
- const db = getDb();
228
- const candidate = db
229
- .select()
230
- .from(memoryItems)
231
- .where(eq(memoryItems.id, "item-candidate-contradiction"))
232
- .get();
233
- const existing = db
234
- .select()
235
- .from(memoryItems)
236
- .where(eq(memoryItems.id, "item-existing-contradiction"))
237
- .get();
238
- const conflicts = db.select().from(memoryItemConflicts).all();
239
-
240
- expect(classifyCallCount).toBe(1);
241
- expect(candidate?.status).toBe("active");
242
- expect(typeof candidate?.validFrom).toBe("number");
243
- expect(typeof existing?.invalidAt).toBe("number");
244
- expect(conflicts).toHaveLength(0);
245
- });
246
-
247
- test("only evaluates contradiction candidates within the same scope", async () => {
248
- nextRelationship = "ambiguous_contradiction";
249
- nextExplanation = "Should not be used for this test.";
250
-
251
- insertMemoryItem({
252
- id: "item-existing-other-scope",
253
- statement: "Use Go for backend services.",
254
- scopeId: "workspace-b",
255
- });
256
- insertMemoryItem({
257
- id: "item-candidate-default-scope",
258
- statement: "Use Rust for backend services.",
259
- scopeId: "workspace-a",
260
- });
261
-
262
- await checkContradictions("item-candidate-default-scope");
263
-
264
- const db = getDb();
265
- const candidate = db
266
- .select()
267
- .from(memoryItems)
268
- .where(eq(memoryItems.id, "item-candidate-default-scope"))
269
- .get();
270
- const conflicts = db.select().from(memoryItemConflicts).all();
271
-
272
- expect(classifyCallCount).toBe(0);
273
- expect(candidate?.status).toBe("active");
274
- expect(conflicts).toHaveLength(0);
275
- });
276
-
277
- test("project kind ambiguous contradiction does not generate pending conflict with default config", async () => {
278
- nextRelationship = "ambiguous_contradiction";
279
- nextExplanation = "Project items may conflict but are not durable.";
280
-
281
- insertMemoryItem({
282
- id: "item-existing-project",
283
- statement: "The backend uses Node.js.",
284
- kind: "project",
285
- });
286
- insertMemoryItem({
287
- id: "item-candidate-project",
288
- statement: "The backend uses Deno.",
289
- kind: "project",
290
- });
291
-
292
- await checkContradictions("item-candidate-project");
293
-
294
- expect(classifyCallCount).toBe(0);
295
- const db = getDb();
296
- const conflicts = db.select().from(memoryItemConflicts).all();
297
- expect(conflicts).toHaveLength(0);
298
- });
299
-
300
- test("skips classification when item kind is not in conflictableKinds", async () => {
301
- mockConflictableKinds = ["instruction", "style"];
302
- nextRelationship = "ambiguous_contradiction";
303
-
304
- insertMemoryItem({
305
- id: "item-existing-ineligible",
306
- statement: "User prefers React for frontend work.",
307
- });
308
- insertMemoryItem({
309
- id: "item-candidate-ineligible",
310
- statement: "User prefers Vue for frontend work.",
311
- });
312
-
313
- await checkContradictions("item-candidate-ineligible");
314
-
315
- expect(classifyCallCount).toBe(0);
316
- const db = getDb();
317
- const conflicts = db.select().from(memoryItemConflicts).all();
318
- expect(conflicts).toHaveLength(0);
319
- });
320
-
321
- test("skips classification when candidate statement contains PR-tracking content", async () => {
322
- nextRelationship = "ambiguous_contradiction";
323
-
324
- insertMemoryItem({
325
- id: "item-existing-pr-tracking",
326
- statement: "Track PR #5526 for review.",
327
- });
328
- insertMemoryItem({
329
- id: "item-candidate-pr-tracking",
330
- statement: "Track PR #5525 for review.",
331
- });
332
-
333
- await checkContradictions("item-candidate-pr-tracking");
334
-
335
- expect(classifyCallCount).toBe(0);
336
- const db = getDb();
337
- const conflicts = db.select().from(memoryItemConflicts).all();
338
- expect(conflicts).toHaveLength(0);
339
- });
340
-
341
- test("durable preference contradiction still runs normal flow", async () => {
342
- nextRelationship = "ambiguous_contradiction";
343
- nextExplanation = "Both are valid preferences that conflict.";
344
-
345
- insertMemoryItem({
346
- id: "item-existing-durable",
347
- statement: "User prefers React for frontend work.",
348
- });
349
- insertMemoryItem({
350
- id: "item-candidate-durable",
351
- statement: "User prefers Vue for frontend work.",
352
- });
353
-
354
- await checkContradictions("item-candidate-durable");
355
-
356
- expect(classifyCallCount).toBe(1);
357
- const db = getDb();
358
- const conflicts = db.select().from(memoryItemConflicts).all();
359
- expect(conflicts).toHaveLength(1);
360
- });
361
- });
@@ -1,211 +0,0 @@
1
- import { mkdtempSync, rmSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import {
5
- afterAll,
6
- beforeAll,
7
- beforeEach,
8
- describe,
9
- expect,
10
- mock,
11
- test,
12
- } from "bun:test";
13
-
14
- import { and, eq } from "drizzle-orm";
15
-
16
- const testDir = mkdtempSync(join(tmpdir(), "entity-extractor-test-"));
17
-
18
- mock.module("../util/platform.js", () => ({
19
- getDataDir: () => testDir,
20
- isMacOS: () => process.platform === "darwin",
21
- isLinux: () => process.platform === "linux",
22
- isWindows: () => process.platform === "win32",
23
- getPidPath: () => join(testDir, "test.pid"),
24
- getDbPath: () => join(testDir, "test.db"),
25
- getLogPath: () => join(testDir, "test.log"),
26
- ensureDataDir: () => {},
27
- }));
28
-
29
- mock.module("../util/logger.js", () => ({
30
- getLogger: () =>
31
- new Proxy({} as Record<string, unknown>, {
32
- get: () => () => {},
33
- }),
34
- }));
35
-
36
- import { getDb, initializeDb, resetDb } from "../memory/db.js";
37
- import {
38
- resolveEntityName,
39
- upsertEntity,
40
- upsertEntityRelation,
41
- } from "../memory/entity-extractor.js";
42
- import { memoryEntities, memoryEntityRelations } from "../memory/schema.js";
43
-
44
- describe("entity extractor helpers", () => {
45
- beforeAll(() => {
46
- initializeDb();
47
- });
48
-
49
- beforeEach(() => {
50
- const db = getDb();
51
- db.run("DELETE FROM memory_item_entities");
52
- db.run("DELETE FROM memory_entity_relations");
53
- db.run("DELETE FROM memory_entities");
54
- db.run("DELETE FROM memory_checkpoints");
55
- });
56
-
57
- afterAll(() => {
58
- resetDb();
59
- try {
60
- rmSync(testDir, { recursive: true, force: true });
61
- } catch {
62
- // best effort cleanup
63
- }
64
- });
65
-
66
- test("upsertEntity reuses existing row via alias matching", () => {
67
- const db = getDb();
68
-
69
- const firstId = upsertEntity({
70
- name: "VS Code",
71
- type: "tool",
72
- aliases: ["vscode"],
73
- });
74
- const secondId = upsertEntity({
75
- name: "Visual Studio Code",
76
- type: "tool",
77
- aliases: ["VS Code", "vscode"],
78
- });
79
-
80
- expect(secondId).toBe(firstId);
81
- expect(resolveEntityName("vscode")).toBe(firstId);
82
- expect(resolveEntityName("VS Code")).toBe(firstId);
83
-
84
- const stored = db
85
- .select()
86
- .from(memoryEntities)
87
- .where(eq(memoryEntities.id, firstId))
88
- .get();
89
-
90
- expect(stored).toBeDefined();
91
- expect(stored!.mentionCount).toBe(2);
92
- const aliases = stored!.aliases
93
- ? (JSON.parse(stored!.aliases) as string[])
94
- : [];
95
- expect(aliases).toContain("vscode");
96
- });
97
-
98
- test("upsertEntityRelation merges duplicate edges by uniqueness key", () => {
99
- const db = getDb();
100
- const sourceEntityId = upsertEntity({
101
- name: "Project Atlas",
102
- type: "project",
103
- aliases: ["atlas"],
104
- });
105
- const targetEntityId = upsertEntity({
106
- name: "Qdrant",
107
- type: "tool",
108
- aliases: [],
109
- });
110
-
111
- upsertEntityRelation({
112
- sourceEntityId,
113
- targetEntityId,
114
- relation: "uses",
115
- evidence: "Project Atlas uses Qdrant for memory search",
116
- seenAt: 1_700_000_000_000,
117
- });
118
- upsertEntityRelation({
119
- sourceEntityId,
120
- targetEntityId,
121
- relation: "uses",
122
- evidence: null,
123
- seenAt: 1_700_000_100_000,
124
- });
125
- upsertEntityRelation({
126
- sourceEntityId,
127
- targetEntityId,
128
- relation: "uses",
129
- evidence: "Atlas still depends on Qdrant",
130
- seenAt: 1_700_000_200_000,
131
- });
132
-
133
- const relationRows = db
134
- .select()
135
- .from(memoryEntityRelations)
136
- .where(
137
- and(
138
- eq(memoryEntityRelations.sourceEntityId, sourceEntityId),
139
- eq(memoryEntityRelations.targetEntityId, targetEntityId),
140
- eq(memoryEntityRelations.relation, "uses"),
141
- ),
142
- )
143
- .all();
144
-
145
- expect(relationRows.length).toBe(1);
146
- expect(relationRows[0].firstSeenAt).toBe(1_700_000_000_000);
147
- expect(relationRows[0].lastSeenAt).toBe(1_700_000_200_000);
148
- expect(relationRows[0].evidence).toBe("Atlas still depends on Qdrant");
149
- });
150
-
151
- test("resolveEntityName prefers exact canonical name over alias match", () => {
152
- const db = getDb();
153
- const now = Date.now();
154
-
155
- // Insert two distinct entities directly to avoid upsertEntity dedupe.
156
- const aliasEntityId = crypto.randomUUID();
157
- db.insert(memoryEntities)
158
- .values({
159
- id: aliasEntityId,
160
- name: "React Native",
161
- type: "tool",
162
- aliases: JSON.stringify(["React", "RN"]),
163
- description: null,
164
- firstSeenAt: now,
165
- lastSeenAt: now,
166
- mentionCount: 1,
167
- })
168
- .run();
169
-
170
- const canonicalEntityId = crypto.randomUUID();
171
- db.insert(memoryEntities)
172
- .values({
173
- id: canonicalEntityId,
174
- name: "React",
175
- type: "tool",
176
- aliases: JSON.stringify(["ReactJS"]),
177
- description: null,
178
- firstSeenAt: now,
179
- lastSeenAt: now,
180
- mentionCount: 1,
181
- })
182
- .run();
183
-
184
- // resolveEntityName("React") should return the entity whose canonical
185
- // name is "React", not the one that merely lists it as an alias.
186
- expect(resolveEntityName("React")).toBe(canonicalEntityId);
187
- expect(resolveEntityName("react")).toBe(canonicalEntityId);
188
- // Alias-only lookups still work
189
- expect(resolveEntityName("RN")).toBe(aliasEntityId);
190
- expect(resolveEntityName("ReactJS")).toBe(canonicalEntityId);
191
- });
192
-
193
- test("upsertEntityRelation drops self-edges", () => {
194
- const db = getDb();
195
- const entityId = upsertEntity({
196
- name: "Sidd",
197
- type: "person",
198
- aliases: [],
199
- });
200
-
201
- upsertEntityRelation({
202
- sourceEntityId: entityId,
203
- targetEntityId: entityId,
204
- relation: "collaborates_with",
205
- evidence: "self edge should not be stored",
206
- });
207
-
208
- const relationRows = db.select().from(memoryEntityRelations).all();
209
- expect(relationRows.length).toBe(0);
210
- });
211
- });