@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,269 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import {
4
- isConflictKindEligible,
5
- isConflictKindPairEligible,
6
- isConflictUserEvidenced,
7
- isDurableInstructionStatement,
8
- isStatementConflictEligible,
9
- isTransientTrackingStatement,
10
- isUserEvidencedVerificationState,
11
- } from "../memory/conflict-policy.js";
12
-
13
- describe("conflict-policy", () => {
14
- const config = { conflictableKinds: ["preference", "profile", "constraint"] };
15
-
16
- describe("isConflictKindEligible", () => {
17
- test("returns true for eligible kind", () => {
18
- expect(isConflictKindEligible("preference", config)).toBe(true);
19
- expect(isConflictKindEligible("profile", config)).toBe(true);
20
- expect(isConflictKindEligible("constraint", config)).toBe(true);
21
- });
22
-
23
- test("returns false for ineligible kind", () => {
24
- expect(isConflictKindEligible("project", config)).toBe(false);
25
- expect(isConflictKindEligible("todo", config)).toBe(false);
26
- expect(isConflictKindEligible("fact", config)).toBe(false);
27
- });
28
- });
29
-
30
- describe("isConflictKindPairEligible", () => {
31
- test("returns true when both kinds are eligible", () => {
32
- expect(isConflictKindPairEligible("preference", "profile", config)).toBe(
33
- true,
34
- );
35
- });
36
-
37
- test("returns false when existing kind is ineligible", () => {
38
- expect(isConflictKindPairEligible("project", "preference", config)).toBe(
39
- false,
40
- );
41
- });
42
-
43
- test("returns false when candidate kind is ineligible", () => {
44
- expect(isConflictKindPairEligible("preference", "todo", config)).toBe(
45
- false,
46
- );
47
- });
48
-
49
- test("returns false when both kinds are ineligible", () => {
50
- expect(isConflictKindPairEligible("project", "todo", config)).toBe(false);
51
- });
52
- });
53
-
54
- describe("isTransientTrackingStatement", () => {
55
- test("detects PR URLs", () => {
56
- expect(
57
- isTransientTrackingStatement(
58
- "Track https://github.com/org/repo/pull/5526",
59
- ),
60
- ).toBe(true);
61
- });
62
-
63
- test("detects issue/ticket references", () => {
64
- expect(isTransientTrackingStatement("Track PR #5526 and #5525")).toBe(
65
- true,
66
- );
67
- expect(isTransientTrackingStatement("See issue #42 for details")).toBe(
68
- true,
69
- );
70
- expect(isTransientTrackingStatement("Filed ticket 1234")).toBe(true);
71
- });
72
-
73
- test("detects tracking language", () => {
74
- expect(isTransientTrackingStatement("While we wait for CI to pass")).toBe(
75
- true,
76
- );
77
- expect(isTransientTrackingStatement("This PR needs review")).toBe(true);
78
- });
79
-
80
- test("does not flag generic time words as transient", () => {
81
- expect(isTransientTrackingStatement("The deadline is today")).toBe(false);
82
- expect(isTransientTrackingStatement("I need this right now")).toBe(false);
83
- });
84
-
85
- test("does not flag durable statements", () => {
86
- expect(
87
- isTransientTrackingStatement(
88
- "Always answer with concise bullet points",
89
- ),
90
- ).toBe(false);
91
- expect(isTransientTrackingStatement("User prefers dark mode")).toBe(
92
- false,
93
- );
94
- });
95
-
96
- test("does not false-positive on non-PR URLs", () => {
97
- expect(
98
- isTransientTrackingStatement("Visit https://example.com for docs"),
99
- ).toBe(false);
100
- });
101
- });
102
-
103
- describe("isDurableInstructionStatement", () => {
104
- test("detects durable instruction cues", () => {
105
- expect(
106
- isDurableInstructionStatement(
107
- "Always answer with concise bullet points",
108
- ),
109
- ).toBe(true);
110
- expect(
111
- isDurableInstructionStatement("Never use semicolons in JavaScript"),
112
- ).toBe(true);
113
- expect(
114
- isDurableInstructionStatement("Use concise format for status updates"),
115
- ).toBe(true);
116
- expect(
117
- isDurableInstructionStatement("The default database is Postgres"),
118
- ).toBe(true);
119
- });
120
-
121
- test("rejects statements without durable cues", () => {
122
- expect(isDurableInstructionStatement("Check the build output")).toBe(
123
- false,
124
- );
125
- expect(isDurableInstructionStatement("Run the migration script")).toBe(
126
- false,
127
- );
128
- });
129
- });
130
-
131
- describe("isStatementConflictEligible", () => {
132
- test("rejects transient statements for any kind", () => {
133
- expect(isStatementConflictEligible("preference", "Track PR #5526")).toBe(
134
- false,
135
- );
136
- expect(
137
- isStatementConflictEligible("instruction", "This PR needs review"),
138
- ).toBe(false);
139
- });
140
-
141
- test("accepts durable instruction statements", () => {
142
- expect(
143
- isStatementConflictEligible(
144
- "instruction",
145
- "Always use TypeScript strict mode",
146
- ),
147
- ).toBe(true);
148
- expect(
149
- isStatementConflictEligible("style", "Default to concise format"),
150
- ).toBe(true);
151
- });
152
-
153
- test("rejects non-durable instruction statements", () => {
154
- expect(
155
- isStatementConflictEligible("instruction", "Run the build first"),
156
- ).toBe(false);
157
- expect(isStatementConflictEligible("style", "Check the output")).toBe(
158
- false,
159
- );
160
- });
161
-
162
- test("accepts non-transient statements for non-instruction kinds", () => {
163
- expect(
164
- isStatementConflictEligible("preference", "User prefers dark mode"),
165
- ).toBe(true);
166
- expect(
167
- isStatementConflictEligible("fact", "User works at Acme Corp"),
168
- ).toBe(true);
169
- });
170
-
171
- test("rejects kinds not in conflictableKinds when config is provided", () => {
172
- const policyConfig = { conflictableKinds: ["preference", "profile"] };
173
- expect(
174
- isStatementConflictEligible(
175
- "fact",
176
- "User works at Acme Corp",
177
- policyConfig,
178
- ),
179
- ).toBe(false);
180
- expect(
181
- isStatementConflictEligible(
182
- "preference",
183
- "User prefers dark mode",
184
- policyConfig,
185
- ),
186
- ).toBe(true);
187
- });
188
-
189
- test("skips kind check when config is omitted", () => {
190
- expect(
191
- isStatementConflictEligible("fact", "User works at Acme Corp"),
192
- ).toBe(true);
193
- });
194
- });
195
-
196
- describe("isUserEvidencedVerificationState", () => {
197
- test("accepts user_reported", () => {
198
- expect(isUserEvidencedVerificationState("user_reported")).toBe(true);
199
- });
200
-
201
- test("accepts user_confirmed", () => {
202
- expect(isUserEvidencedVerificationState("user_confirmed")).toBe(true);
203
- });
204
-
205
- test("accepts legacy_import", () => {
206
- expect(isUserEvidencedVerificationState("legacy_import")).toBe(true);
207
- });
208
-
209
- test("rejects assistant_inferred", () => {
210
- expect(isUserEvidencedVerificationState("assistant_inferred")).toBe(
211
- false,
212
- );
213
- });
214
-
215
- test("rejects unknown states", () => {
216
- expect(isUserEvidencedVerificationState("")).toBe(false);
217
- expect(isUserEvidencedVerificationState("auto_detected")).toBe(false);
218
- expect(isUserEvidencedVerificationState("pending")).toBe(false);
219
- });
220
- });
221
-
222
- describe("isConflictUserEvidenced", () => {
223
- test("returns true when existing side is user-evidenced", () => {
224
- expect(
225
- isConflictUserEvidenced("user_reported", "assistant_inferred"),
226
- ).toBe(true);
227
- expect(
228
- isConflictUserEvidenced("user_confirmed", "assistant_inferred"),
229
- ).toBe(true);
230
- expect(
231
- isConflictUserEvidenced("legacy_import", "assistant_inferred"),
232
- ).toBe(true);
233
- });
234
-
235
- test("returns true when candidate side is user-evidenced", () => {
236
- expect(
237
- isConflictUserEvidenced("assistant_inferred", "user_reported"),
238
- ).toBe(true);
239
- expect(
240
- isConflictUserEvidenced("assistant_inferred", "user_confirmed"),
241
- ).toBe(true);
242
- expect(
243
- isConflictUserEvidenced("assistant_inferred", "legacy_import"),
244
- ).toBe(true);
245
- });
246
-
247
- test("returns true when both sides are user-evidenced", () => {
248
- expect(isConflictUserEvidenced("user_reported", "user_confirmed")).toBe(
249
- true,
250
- );
251
- expect(isConflictUserEvidenced("legacy_import", "user_reported")).toBe(
252
- true,
253
- );
254
- });
255
-
256
- test("returns false when neither side is user-evidenced", () => {
257
- expect(
258
- isConflictUserEvidenced("assistant_inferred", "assistant_inferred"),
259
- ).toBe(false);
260
- });
261
-
262
- test("returns false for unknown states on both sides", () => {
263
- expect(isConflictUserEvidenced("auto_detected", "pending")).toBe(false);
264
- expect(
265
- isConflictUserEvidenced("assistant_inferred", "auto_detected"),
266
- ).toBe(false);
267
- });
268
- });
269
- });
@@ -1,372 +0,0 @@
1
- import { mkdtempSync, rmSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
5
-
6
- import { eq } from "drizzle-orm";
7
-
8
- const testDir = mkdtempSync(join(tmpdir(), "conflict-store-test-"));
9
-
10
- mock.module("../util/platform.js", () => ({
11
- getDataDir: () => testDir,
12
- isMacOS: () => process.platform === "darwin",
13
- isLinux: () => process.platform === "linux",
14
- isWindows: () => process.platform === "win32",
15
- getPidPath: () => join(testDir, "test.pid"),
16
- getDbPath: () => join(testDir, "test.db"),
17
- getLogPath: () => join(testDir, "test.log"),
18
- ensureDataDir: () => {},
19
- }));
20
-
21
- mock.module("../util/logger.js", () => ({
22
- getLogger: () =>
23
- new Proxy({} as Record<string, unknown>, {
24
- get: () => () => {},
25
- }),
26
- }));
27
-
28
- import {
29
- applyConflictResolution,
30
- createOrUpdatePendingConflict,
31
- getConflictById,
32
- getPendingConflictByPair,
33
- listPendingConflictDetails,
34
- listPendingConflicts,
35
- resolveConflict,
36
- } from "../memory/conflict-store.js";
37
- import { getDb, initializeDb, resetDb } from "../memory/db.js";
38
- import { memoryItems } from "../memory/schema.js";
39
-
40
- initializeDb();
41
-
42
- afterAll(() => {
43
- resetDb();
44
- try {
45
- rmSync(testDir, { recursive: true });
46
- } catch {
47
- /* best effort */
48
- }
49
- });
50
-
51
- function resetTables() {
52
- const db = getDb();
53
- db.run("DELETE FROM memory_item_conflicts");
54
- db.run("DELETE FROM memory_item_sources");
55
- db.run("DELETE FROM memory_items");
56
- }
57
-
58
- function insertItemPair(
59
- suffix: string,
60
- scopeId = "default",
61
- opts?: {
62
- existingVerificationState?: string;
63
- candidateVerificationState?: string;
64
- },
65
- ): { existingItemId: string; candidateItemId: string } {
66
- const db = getDb();
67
- const now = Date.now();
68
- const existingItemId = `existing-${suffix}`;
69
- const candidateItemId = `candidate-${suffix}`;
70
- db.insert(memoryItems)
71
- .values([
72
- {
73
- id: existingItemId,
74
- kind: "fact",
75
- subject: "framework preference",
76
- statement: `Existing statement ${suffix}`,
77
- status: "active",
78
- confidence: 0.8,
79
- importance: 0.5,
80
- fingerprint: `fp-existing-${suffix}`,
81
- verificationState:
82
- opts?.existingVerificationState ?? "assistant_inferred",
83
- scopeId,
84
- firstSeenAt: now,
85
- lastSeenAt: now,
86
- },
87
- {
88
- id: candidateItemId,
89
- kind: "fact",
90
- subject: "framework preference",
91
- statement: `Candidate statement ${suffix}`,
92
- status: "pending_clarification",
93
- confidence: 0.8,
94
- importance: 0.5,
95
- fingerprint: `fp-candidate-${suffix}`,
96
- verificationState:
97
- opts?.candidateVerificationState ?? "assistant_inferred",
98
- scopeId,
99
- firstSeenAt: now,
100
- lastSeenAt: now,
101
- },
102
- ])
103
- .run();
104
-
105
- return { existingItemId, candidateItemId };
106
- }
107
-
108
- describe("conflict-store", () => {
109
- beforeEach(() => {
110
- resetTables();
111
- });
112
-
113
- test("creates and fetches a pending conflict", () => {
114
- const pair = insertItemPair("create");
115
- const conflict = createOrUpdatePendingConflict({
116
- scopeId: "default",
117
- existingItemId: pair.existingItemId,
118
- candidateItemId: pair.candidateItemId,
119
- relationship: "ambiguous_contradiction",
120
- clarificationQuestion: "Do you prefer React or Vue?",
121
- });
122
-
123
- expect(conflict.id).toBeDefined();
124
- expect(conflict.status).toBe("pending_clarification");
125
- expect(conflict.scopeId).toBe("default");
126
- expect(conflict.relationship).toBe("ambiguous_contradiction");
127
- expect(conflict.clarificationQuestion).toBe("Do you prefer React or Vue?");
128
-
129
- const byPair = getPendingConflictByPair(
130
- "default",
131
- pair.existingItemId,
132
- pair.candidateItemId,
133
- );
134
- expect(byPair?.id).toBe(conflict.id);
135
- });
136
-
137
- test("deduplicates unresolved pair and updates fields in place", () => {
138
- const pair = insertItemPair("dedupe");
139
- const first = createOrUpdatePendingConflict({
140
- scopeId: "default",
141
- existingItemId: pair.existingItemId,
142
- candidateItemId: pair.candidateItemId,
143
- relationship: "contradiction",
144
- clarificationQuestion: "First question",
145
- });
146
-
147
- const second = createOrUpdatePendingConflict({
148
- scopeId: "default",
149
- existingItemId: pair.existingItemId,
150
- candidateItemId: pair.candidateItemId,
151
- relationship: "ambiguous_contradiction",
152
- clarificationQuestion: "Second question",
153
- });
154
-
155
- expect(second.id).toBe(first.id);
156
- expect(second.relationship).toBe("ambiguous_contradiction");
157
- expect(second.clarificationQuestion).toBe("Second question");
158
- expect(listPendingConflicts("default")).toHaveLength(1);
159
- });
160
-
161
- test("allows a new pending row for the same pair after resolution", () => {
162
- const pair = insertItemPair("reopen");
163
- const first = createOrUpdatePendingConflict({
164
- scopeId: "default",
165
- existingItemId: pair.existingItemId,
166
- candidateItemId: pair.candidateItemId,
167
- relationship: "ambiguous_contradiction",
168
- });
169
-
170
- const resolved = resolveConflict(first.id, {
171
- status: "resolved_keep_existing",
172
- resolutionNote: "User confirmed existing statement is correct.",
173
- });
174
- expect(resolved?.status).toBe("resolved_keep_existing");
175
- expect(typeof resolved?.resolvedAt).toBe("number");
176
-
177
- const reopened = createOrUpdatePendingConflict({
178
- scopeId: "default",
179
- existingItemId: pair.existingItemId,
180
- candidateItemId: pair.candidateItemId,
181
- relationship: "ambiguous_contradiction",
182
- clarificationQuestion: "Please confirm again",
183
- });
184
-
185
- expect(reopened.id).not.toBe(first.id);
186
- expect(getConflictById(first.id)?.status).toBe("resolved_keep_existing");
187
- expect(listPendingConflicts("default")).toHaveLength(1);
188
- });
189
-
190
- test("lists only pending conflicts for a scope", () => {
191
- const defaultA = insertItemPair("scope-a");
192
- const defaultB = insertItemPair("scope-b");
193
- const otherScope = insertItemPair("scope-other", "workspace-b");
194
-
195
- const conflictA = createOrUpdatePendingConflict({
196
- scopeId: "default",
197
- existingItemId: defaultA.existingItemId,
198
- candidateItemId: defaultA.candidateItemId,
199
- relationship: "ambiguous_contradiction",
200
- });
201
- const conflictB = createOrUpdatePendingConflict({
202
- scopeId: "default",
203
- existingItemId: defaultB.existingItemId,
204
- candidateItemId: defaultB.candidateItemId,
205
- relationship: "ambiguous_contradiction",
206
- });
207
- createOrUpdatePendingConflict({
208
- scopeId: "workspace-b",
209
- existingItemId: otherScope.existingItemId,
210
- candidateItemId: otherScope.candidateItemId,
211
- relationship: "ambiguous_contradiction",
212
- });
213
-
214
- resolveConflict(conflictB.id, {
215
- status: "dismissed",
216
- resolutionNote: "Irrelevant to active context",
217
- });
218
-
219
- const pendingDefault = listPendingConflicts("default");
220
- expect(pendingDefault).toHaveLength(1);
221
- expect(pendingDefault[0].id).toBe(conflictA.id);
222
- expect(pendingDefault[0].status).toBe("pending_clarification");
223
- });
224
-
225
- test("listPendingConflictDetails joins current statements and verification states", () => {
226
- const pair = insertItemPair("details", "workspace-a", {
227
- existingVerificationState: "user_confirmed",
228
- candidateVerificationState: "assistant_inferred",
229
- });
230
- createOrUpdatePendingConflict({
231
- scopeId: "workspace-a",
232
- existingItemId: pair.existingItemId,
233
- candidateItemId: pair.candidateItemId,
234
- relationship: "ambiguous_contradiction",
235
- clarificationQuestion: "Which framework should I keep?",
236
- });
237
-
238
- const details = listPendingConflictDetails("workspace-a");
239
- expect(details).toHaveLength(1);
240
- expect(details[0].existingStatement).toBe("Existing statement details");
241
- expect(details[0].candidateStatement).toBe("Candidate statement details");
242
- expect(details[0].existingKind).toBe("fact");
243
- expect(details[0].candidateKind).toBe("fact");
244
- expect(details[0].existingVerificationState).toBe("user_confirmed");
245
- expect(details[0].candidateVerificationState).toBe("assistant_inferred");
246
- });
247
-
248
- test("applyConflictResolution keeps candidate and resolves conflict row", () => {
249
- const pair = insertItemPair("apply-candidate");
250
- const conflict = createOrUpdatePendingConflict({
251
- scopeId: "default",
252
- existingItemId: pair.existingItemId,
253
- candidateItemId: pair.candidateItemId,
254
- relationship: "ambiguous_contradiction",
255
- });
256
-
257
- expect(
258
- applyConflictResolution({
259
- conflictId: conflict.id,
260
- resolution: "keep_candidate",
261
- resolutionNote: "User confirmed candidate statement.",
262
- }),
263
- ).toBe(true);
264
-
265
- const db = getDb();
266
- const existing = db
267
- .select()
268
- .from(memoryItems)
269
- .where(eq(memoryItems.id, pair.existingItemId))
270
- .get();
271
- const candidate = db
272
- .select()
273
- .from(memoryItems)
274
- .where(eq(memoryItems.id, pair.candidateItemId))
275
- .get();
276
- const updatedConflict = getConflictById(conflict.id);
277
-
278
- expect(typeof existing?.invalidAt).toBe("number");
279
- expect(existing?.status).toBe("superseded");
280
- expect(candidate?.status).toBe("active");
281
- expect(updatedConflict?.status).toBe("resolved_keep_candidate");
282
- });
283
-
284
- test("applyConflictResolution merge updates existing item statement", () => {
285
- const pair = insertItemPair("apply-merge");
286
- const conflict = createOrUpdatePendingConflict({
287
- scopeId: "default",
288
- existingItemId: pair.existingItemId,
289
- candidateItemId: pair.candidateItemId,
290
- relationship: "ambiguous_contradiction",
291
- });
292
-
293
- const merged = "Use React for dashboard pages and Vue for marketing pages.";
294
- expect(
295
- applyConflictResolution({
296
- conflictId: conflict.id,
297
- resolution: "merge",
298
- mergedStatement: merged,
299
- resolutionNote: "User clarified both apply in different contexts.",
300
- }),
301
- ).toBe(true);
302
-
303
- const db = getDb();
304
- const existing = db
305
- .select()
306
- .from(memoryItems)
307
- .where(eq(memoryItems.id, pair.existingItemId))
308
- .get();
309
- const candidate = db
310
- .select()
311
- .from(memoryItems)
312
- .where(eq(memoryItems.id, pair.candidateItemId))
313
- .get();
314
- const updatedConflict = getConflictById(conflict.id);
315
-
316
- expect(existing?.statement).toBe(merged);
317
- expect(candidate?.status).toBe("superseded");
318
- expect(updatedConflict?.status).toBe("resolved_merge");
319
- });
320
-
321
- test("enforces pending-pair uniqueness with a partial index", () => {
322
- const pair = insertItemPair("index");
323
- const raw = (
324
- getDb() as unknown as {
325
- $client: import("bun:sqlite").Database;
326
- }
327
- ).$client;
328
- const now = Date.now();
329
-
330
- raw.run(
331
- `INSERT INTO memory_item_conflicts (
332
- id, scope_id, existing_item_id, candidate_item_id, relationship, status,
333
- clarification_question, resolution_note, last_asked_at, resolved_at, created_at, updated_at
334
- ) VALUES (
335
- 'conflict-index-a', 'default', '${pair.existingItemId}', '${pair.candidateItemId}',
336
- 'ambiguous_contradiction', 'pending_clarification', NULL, NULL, NULL, NULL, ${now}, ${now}
337
- )`,
338
- );
339
-
340
- expect(() => {
341
- raw.run(
342
- `INSERT INTO memory_item_conflicts (
343
- id, scope_id, existing_item_id, candidate_item_id, relationship, status,
344
- clarification_question, resolution_note, last_asked_at, resolved_at, created_at, updated_at
345
- ) VALUES (
346
- 'conflict-index-b', 'default', '${pair.existingItemId}', '${
347
- pair.candidateItemId
348
- }',
349
- 'ambiguous_contradiction', 'pending_clarification', NULL, NULL, NULL, NULL, ${
350
- now + 1
351
- }, ${now + 1}
352
- )`,
353
- );
354
- }).toThrow();
355
-
356
- expect(() => {
357
- raw.run(
358
- `INSERT INTO memory_item_conflicts (
359
- id, scope_id, existing_item_id, candidate_item_id, relationship, status,
360
- clarification_question, resolution_note, last_asked_at, resolved_at, created_at, updated_at
361
- ) VALUES (
362
- 'conflict-index-c', 'default', '${pair.existingItemId}', '${
363
- pair.candidateItemId
364
- }',
365
- 'ambiguous_contradiction', 'resolved_keep_candidate', NULL, NULL, NULL, ${
366
- now + 2
367
- }, ${now + 2}, ${now + 2}
368
- )`,
369
- );
370
- }).not.toThrow();
371
- });
372
- });