@vellumai/assistant 0.5.13 → 0.5.14

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 (425) hide show
  1. package/.env.example +1 -6
  2. package/AGENTS.md +4 -0
  3. package/ARCHITECTURE.md +0 -1
  4. package/bunfig.toml +1 -0
  5. package/docs/architecture/memory.md +3 -3
  6. package/openapi.yaml +127 -22
  7. package/package.json +1 -1
  8. package/src/__tests__/access-request-decision.test.ts +2 -32
  9. package/src/__tests__/actor-token-service.test.ts +1 -31
  10. package/src/__tests__/anthropic-provider.test.ts +53 -40
  11. package/src/__tests__/app-git-history.test.ts +9 -17
  12. package/src/__tests__/app-git-service.test.ts +14 -20
  13. package/src/__tests__/app-store-dir-names.test.ts +10 -20
  14. package/src/__tests__/approval-cascade.test.ts +2 -19
  15. package/src/__tests__/approval-primitive.test.ts +2 -27
  16. package/src/__tests__/approval-routes-http.test.ts +2 -30
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -28
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -45
  19. package/src/__tests__/attachments-store.test.ts +5 -32
  20. package/src/__tests__/audit-log-rotation.test.ts +5 -36
  21. package/src/__tests__/avatar-e2e.test.ts +1 -9
  22. package/src/__tests__/avatar-generator.test.ts +1 -7
  23. package/src/__tests__/browser-fill-credential.test.ts +0 -4
  24. package/src/__tests__/browser-manager.test.ts +0 -6
  25. package/src/__tests__/call-controller.test.ts +1 -22
  26. package/src/__tests__/call-conversation-messages.test.ts +0 -21
  27. package/src/__tests__/call-domain.test.ts +0 -25
  28. package/src/__tests__/call-pointer-messages.test.ts +0 -21
  29. package/src/__tests__/call-recovery.test.ts +0 -22
  30. package/src/__tests__/call-routes-http.test.ts +0 -24
  31. package/src/__tests__/call-store.test.ts +0 -21
  32. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -24
  33. package/src/__tests__/canonical-guardian-store.test.ts +48 -21
  34. package/src/__tests__/channel-approval-routes.test.ts +6 -26
  35. package/src/__tests__/channel-approvals.test.ts +1 -38
  36. package/src/__tests__/channel-delivery-store.test.ts +0 -21
  37. package/src/__tests__/channel-guardian.test.ts +0 -26
  38. package/src/__tests__/channel-reply-delivery.test.ts +5 -0
  39. package/src/__tests__/channel-retry-sweep.test.ts +0 -21
  40. package/src/__tests__/checker.test.ts +26 -61
  41. package/src/__tests__/clawhub.test.ts +9 -25
  42. package/src/__tests__/cli-command-risk-guard.test.ts +0 -18
  43. package/src/__tests__/config-loader-backfill.test.ts +9 -28
  44. package/src/__tests__/config-schema-cmd.test.ts +5 -25
  45. package/src/__tests__/config-schema.test.ts +21 -40
  46. package/src/__tests__/config-watcher.test.ts +4 -91
  47. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -21
  48. package/src/__tests__/contacts-tools.test.ts +0 -21
  49. package/src/__tests__/context-memory-e2e.test.ts +0 -21
  50. package/src/__tests__/context-window-manager.test.ts +130 -3
  51. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -4
  52. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -4
  53. package/src/__tests__/conversation-agent-loop.test.ts +0 -4
  54. package/src/__tests__/conversation-attachments.test.ts +1 -24
  55. package/src/__tests__/conversation-attention-store.test.ts +0 -21
  56. package/src/__tests__/conversation-attention-telegram.test.ts +0 -22
  57. package/src/__tests__/conversation-clear-safety.test.ts +0 -22
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -21
  59. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -24
  60. package/src/__tests__/conversation-disk-view-integration.test.ts +1 -23
  61. package/src/__tests__/conversation-disk-view.test.ts +5 -27
  62. package/src/__tests__/conversation-error.test.ts +1 -1
  63. package/src/__tests__/conversation-fork-crud.test.ts +1 -33
  64. package/src/__tests__/conversation-fork-route.test.ts +0 -27
  65. package/src/__tests__/conversation-history-web-search.test.ts +23 -16
  66. package/src/__tests__/conversation-init.benchmark.test.ts +22 -43
  67. package/src/__tests__/conversation-key-store-disk-view.test.ts +8 -34
  68. package/src/__tests__/conversation-load-history-repair.test.ts +0 -4
  69. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -4
  70. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -4
  71. package/src/__tests__/conversation-queue.test.ts +8 -8
  72. package/src/__tests__/conversation-routes-disk-view.test.ts +13 -51
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +64 -38
  74. package/src/__tests__/conversation-slash-commands.test.ts +5 -0
  75. package/src/__tests__/conversation-slash-queue.test.ts +0 -4
  76. package/src/__tests__/conversation-slash-unknown.test.ts +0 -4
  77. package/src/__tests__/conversation-speed-override.test.ts +326 -0
  78. package/src/__tests__/conversation-starter-routes.test.ts +0 -23
  79. package/src/__tests__/conversation-store.test.ts +0 -21
  80. package/src/__tests__/conversation-unread-route.test.ts +0 -24
  81. package/src/__tests__/conversation-usage.test.ts +56 -21
  82. package/src/__tests__/conversation-wipe.test.ts +0 -21
  83. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
  84. package/src/__tests__/conversation-workspace-injection.test.ts +0 -4
  85. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -4
  86. package/src/__tests__/credential-execution-shell-lockdown.test.ts +8 -5
  87. package/src/__tests__/credential-vault-unit.test.ts +9 -428
  88. package/src/__tests__/credentials-cli.test.ts +10 -10
  89. package/src/__tests__/daemon-assistant-events.test.ts +0 -19
  90. package/src/__tests__/date-context.test.ts +77 -97
  91. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +7 -24
  92. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +29 -42
  93. package/src/__tests__/delete-managed-skill-tool.test.ts +2 -10
  94. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -26
  95. package/src/__tests__/docker-signing-key-bootstrap.test.ts +61 -15
  96. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -36
  97. package/src/__tests__/email-cli.test.ts +6 -6
  98. package/src/__tests__/ephemeral-permissions.test.ts +5 -17
  99. package/src/__tests__/first-greeting.test.ts +4 -32
  100. package/src/__tests__/followup-tools.test.ts +0 -21
  101. package/src/__tests__/gateway-only-enforcement.test.ts +0 -20
  102. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -23
  103. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -23
  104. package/src/__tests__/guardian-action-followup-store.test.ts +0 -21
  105. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -21
  106. package/src/__tests__/guardian-action-late-reply.test.ts +0 -21
  107. package/src/__tests__/guardian-action-store.test.ts +0 -21
  108. package/src/__tests__/guardian-action-sweep.test.ts +0 -21
  109. package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -23
  110. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -22
  111. package/src/__tests__/guardian-dispatch.test.ts +0 -21
  112. package/src/__tests__/guardian-grant-minting.test.ts +0 -22
  113. package/src/__tests__/guardian-outbound-http.test.ts +0 -22
  114. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -23
  115. package/src/__tests__/guardian-routing-invariants.test.ts +0 -22
  116. package/src/__tests__/guardian-routing-state.test.ts +0 -22
  117. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -24
  118. package/src/__tests__/headless-browser-interactions.test.ts +0 -4
  119. package/src/__tests__/headless-browser-navigate.test.ts +0 -4
  120. package/src/__tests__/headless-browser-read-tools.test.ts +0 -4
  121. package/src/__tests__/headless-browser-snapshot.test.ts +0 -4
  122. package/src/__tests__/heartbeat-service.test.ts +99 -26
  123. package/src/__tests__/hooks-blocking.test.ts +3 -3
  124. package/src/__tests__/hooks-config.test.ts +7 -7
  125. package/src/__tests__/hooks-discovery.test.ts +3 -3
  126. package/src/__tests__/hooks-integration.test.ts +5 -5
  127. package/src/__tests__/hooks-manager.test.ts +3 -3
  128. package/src/__tests__/hooks-runner.test.ts +5 -23
  129. package/src/__tests__/hooks-settings.test.ts +3 -3
  130. package/src/__tests__/hooks-templates.test.ts +3 -3
  131. package/src/__tests__/http-conversation-lineage.test.ts +0 -27
  132. package/src/__tests__/identity-intro-cache.test.ts +0 -4
  133. package/src/__tests__/inbound-invite-redemption.test.ts +0 -22
  134. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -16
  135. package/src/__tests__/intent-routing.test.ts +2 -55
  136. package/src/__tests__/invite-redemption-service.test.ts +0 -21
  137. package/src/__tests__/invite-routes-http.test.ts +0 -21
  138. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +0 -17
  139. package/src/__tests__/journal-context.test.ts +8 -75
  140. package/src/__tests__/list-messages-attachments.test.ts +0 -22
  141. package/src/__tests__/llm-context-route-provider.test.ts +0 -21
  142. package/src/__tests__/llm-request-log-turn-query.test.ts +46 -28
  143. package/src/__tests__/llm-usage-store.test.ts +0 -21
  144. package/src/__tests__/log-export-workspace.test.ts +1 -1
  145. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
  146. package/src/__tests__/managed-store.test.ts +1 -1
  147. package/src/__tests__/mcp-cli.test.ts +7 -10
  148. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -21
  149. package/src/__tests__/memory-jobs-worker-backoff.test.ts +0 -11
  150. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -21
  151. package/src/__tests__/memory-recall-log-store.test.ts +0 -27
  152. package/src/__tests__/memory-recall-quality.test.ts +0 -21
  153. package/src/__tests__/memory-regressions.experimental.test.ts +31 -30
  154. package/src/__tests__/memory-regressions.test.ts +282 -70
  155. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -21
  156. package/src/__tests__/memory-upsert-concurrency.test.ts +0 -21
  157. package/src/__tests__/messaging-send-tool.test.ts +201 -0
  158. package/src/__tests__/migration-cross-version-compatibility.test.ts +18 -13
  159. package/src/__tests__/migration-export-http.test.ts +7 -1
  160. package/src/__tests__/migration-import-commit-http.test.ts +16 -14
  161. package/src/__tests__/migration-import-preflight-http.test.ts +27 -44
  162. package/src/__tests__/migration-validate-http.test.ts +1 -28
  163. package/src/__tests__/native-web-search.test.ts +25 -22
  164. package/src/__tests__/non-member-access-request.test.ts +0 -22
  165. package/src/__tests__/notification-guardian-path.test.ts +0 -21
  166. package/src/__tests__/notification-schedule-dedup.test.ts +1 -25
  167. package/src/__tests__/oauth-apps-routes.test.ts +103 -2
  168. package/src/__tests__/oauth-cli.test.ts +52 -0
  169. package/src/__tests__/oauth-provider-profiles.test.ts +0 -16
  170. package/src/__tests__/oauth-provider-serializer.test.ts +232 -0
  171. package/src/__tests__/oauth-providers-routes.test.ts +257 -0
  172. package/src/__tests__/oauth-store.test.ts +0 -21
  173. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  174. package/src/__tests__/openai-provider.test.ts +261 -0
  175. package/src/__tests__/pairing-concurrent.test.ts +6 -6
  176. package/src/__tests__/pairing-routes.test.ts +7 -1
  177. package/src/__tests__/path-policy.test.ts +1 -1
  178. package/src/__tests__/platform.test.ts +64 -88
  179. package/src/__tests__/playbook-execution.test.ts +0 -21
  180. package/src/__tests__/playbook-tools.test.ts +0 -21
  181. package/src/__tests__/pricing.test.ts +100 -0
  182. package/src/__tests__/relay-server.test.ts +1 -25
  183. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -24
  184. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -24
  185. package/src/__tests__/runtime-events-sse.test.ts +0 -24
  186. package/src/__tests__/sandbox-diagnostics.test.ts +2 -1
  187. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
  188. package/src/__tests__/schedule-store.test.ts +0 -21
  189. package/src/__tests__/schedule-tools.test.ts +0 -21
  190. package/src/__tests__/scheduler-recurrence.test.ts +0 -21
  191. package/src/__tests__/scoped-approval-grants.test.ts +0 -21
  192. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -21
  193. package/src/__tests__/secret-allowlist.test.ts +1 -1
  194. package/src/__tests__/secret-ingress-channel.test.ts +0 -5
  195. package/src/__tests__/secret-ingress-cli.test.ts +0 -6
  196. package/src/__tests__/secret-ingress-http.test.ts +0 -5
  197. package/src/__tests__/secret-ingress.test.ts +0 -5
  198. package/src/__tests__/send-endpoint-busy.test.ts +0 -24
  199. package/src/__tests__/sequence-store.test.ts +0 -21
  200. package/src/__tests__/server-history-render.test.ts +0 -24
  201. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -4
  202. package/src/__tests__/skill-load-inline-command.test.ts +9 -0
  203. package/src/__tests__/skill-load-inline-includes.test.ts +9 -0
  204. package/src/__tests__/skill-load-tool.test.ts +11 -0
  205. package/src/__tests__/skills-uninstall.test.ts +10 -8
  206. package/src/__tests__/skills.test.ts +1 -1
  207. package/src/__tests__/slack-channel-config.test.ts +1 -1
  208. package/src/__tests__/slack-inbound-verification.test.ts +0 -22
  209. package/src/__tests__/starter-bundle.test.ts +4 -1
  210. package/src/__tests__/suggestion-routes.test.ts +2 -0
  211. package/src/__tests__/system-prompt.test.ts +1 -1
  212. package/src/__tests__/terminal-tools.test.ts +1 -1
  213. package/src/__tests__/test-preload.ts +31 -0
  214. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
  215. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  216. package/src/__tests__/tool-executor.test.ts +0 -20
  217. package/src/__tests__/tool-input-summary.test.ts +124 -0
  218. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -1
  219. package/src/__tests__/trust-store.test.ts +7 -1
  220. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -1
  221. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -1
  222. package/src/__tests__/trusted-contact-multichannel.test.ts +1 -1
  223. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  224. package/src/__tests__/turn-boundary-resolution.test.ts +1 -1
  225. package/src/__tests__/twilio-routes.test.ts +1 -1
  226. package/src/__tests__/update-bulletin.test.ts +1 -1
  227. package/src/__tests__/vbundle-pax-and-symlink.test.ts +1 -1
  228. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -0
  229. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
  230. package/src/__tests__/voice-session-bridge.test.ts +1 -1
  231. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -4
  232. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
  233. package/src/__tests__/workspace-migration-down-functions.test.ts +15 -3
  234. package/src/__tests__/workspace-migration-seed-device-id.test.ts +40 -4
  235. package/src/agent/loop.ts +6 -9
  236. package/src/approvals/guardian-decision-primitive.ts +46 -18
  237. package/src/approvals/guardian-request-resolvers.ts +19 -2
  238. package/src/calls/active-call-lease.ts +2 -2
  239. package/src/cli/AGENTS.md +1 -1
  240. package/src/cli/commands/doctor.ts +9 -9
  241. package/src/cli/commands/memory.ts +142 -0
  242. package/src/cli/commands/oauth/__tests__/connect.test.ts +13 -11
  243. package/src/cli/commands/oauth/__tests__/ping.test.ts +1 -1
  244. package/src/cli/commands/oauth/connect.ts +13 -12
  245. package/src/cli/commands/oauth/index.ts +1 -1
  246. package/src/cli/commands/oauth/providers.ts +47 -62
  247. package/src/cli/commands/platform/__tests__/connect.test.ts +72 -46
  248. package/src/cli/commands/platform/__tests__/disconnect.test.ts +54 -1
  249. package/src/cli/commands/platform/__tests__/status.test.ts +36 -0
  250. package/src/cli/commands/platform/connect.ts +17 -7
  251. package/src/cli/commands/platform/disconnect.ts +28 -3
  252. package/src/cli/commands/platform/index.ts +3 -3
  253. package/src/cli.ts +1 -299
  254. package/src/config/assistant-feature-flags.ts +23 -15
  255. package/src/config/bundled-skills/app-builder/TOOLS.json +16 -0
  256. package/src/config/bundled-skills/app-builder/tools/app-create.ts +4 -0
  257. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +5 -1
  258. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +9 -1
  259. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +5 -1
  260. package/src/config/bundled-skills/contacts/TOOLS.json +8 -0
  261. package/src/config/bundled-skills/contacts/tools/contact-search.ts +10 -1
  262. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +16 -2
  263. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +1 -0
  264. package/src/config/bundled-skills/messaging/SKILL.md +7 -7
  265. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +37 -0
  266. package/src/config/bundled-skills/slack/SKILL.md +18 -0
  267. package/src/config/env-registry.ts +15 -11
  268. package/src/config/env.ts +1 -11
  269. package/src/config/feature-flag-registry.json +16 -0
  270. package/src/config/schema.ts +4 -0
  271. package/src/config/schemas/heartbeat.ts +6 -1
  272. package/src/config/schemas/inference.ts +14 -3
  273. package/src/config/schemas/memory-processing.ts +16 -8
  274. package/src/config/schemas/memory-retrieval.ts +3 -3
  275. package/src/config/skills.ts +1 -1
  276. package/src/context/window-manager.ts +174 -51
  277. package/src/credential-execution/executable-discovery.ts +2 -2
  278. package/src/daemon/approved-devices-store.ts +2 -2
  279. package/src/daemon/assistant-attachments.ts +2 -0
  280. package/src/daemon/config-watcher.ts +4 -50
  281. package/src/daemon/conversation-agent-loop-handlers.ts +9 -1
  282. package/src/daemon/conversation-agent-loop.ts +12 -0
  283. package/src/daemon/conversation-error.ts +3 -5
  284. package/src/daemon/conversation-history.ts +7 -3
  285. package/src/daemon/conversation-lifecycle.ts +16 -0
  286. package/src/daemon/conversation-messaging.ts +1 -0
  287. package/src/daemon/conversation-notifiers.ts +67 -30
  288. package/src/daemon/conversation-process.ts +161 -2
  289. package/src/daemon/conversation-queue-manager.ts +2 -0
  290. package/src/daemon/conversation-runtime-assembly.ts +33 -11
  291. package/src/daemon/conversation-slash.ts +14 -3
  292. package/src/daemon/conversation-tool-setup.ts +2 -0
  293. package/src/daemon/conversation-usage.ts +32 -4
  294. package/src/daemon/conversation.ts +33 -1
  295. package/src/daemon/daemon-control.ts +32 -16
  296. package/src/daemon/date-context.ts +47 -45
  297. package/src/daemon/dictation-profile-store.ts +2 -2
  298. package/src/daemon/handlers/conversations.ts +19 -0
  299. package/src/daemon/handlers/shared.ts +14 -21
  300. package/src/daemon/lifecycle.ts +5 -7
  301. package/src/daemon/message-types/conversations.ts +2 -0
  302. package/src/daemon/message-types/guardian-actions.ts +3 -17
  303. package/src/daemon/message-types/integrations.ts +11 -1
  304. package/src/daemon/message-types/messages.ts +1 -0
  305. package/src/daemon/pairing-store.ts +2 -79
  306. package/src/daemon/server.ts +154 -8
  307. package/src/daemon/watch-handler.ts +65 -21
  308. package/src/email/guardrails.ts +3 -3
  309. package/src/heartbeat/heartbeat-service.ts +14 -7
  310. package/src/hooks/cli.ts +2 -2
  311. package/src/hooks/config.ts +2 -2
  312. package/src/hooks/discovery.ts +2 -2
  313. package/src/hooks/manager.ts +2 -2
  314. package/src/hooks/runner.ts +5 -2
  315. package/src/hooks/templates.ts +2 -2
  316. package/src/memory/admin.ts +181 -2
  317. package/src/memory/app-git-service.ts +61 -4
  318. package/src/memory/attachments-store.ts +2 -0
  319. package/src/memory/canonical-guardian-store.ts +16 -0
  320. package/src/memory/db-init.ts +8 -0
  321. package/src/memory/embedding-local.ts +5 -2
  322. package/src/memory/indexer.ts +44 -26
  323. package/src/memory/items-extractor.ts +34 -82
  324. package/src/memory/job-handlers/batch-extraction.ts +741 -0
  325. package/src/memory/job-handlers/journal-carry-forward.test.ts +383 -0
  326. package/src/memory/job-handlers/journal-carry-forward.ts +255 -0
  327. package/src/memory/jobs-store.ts +28 -0
  328. package/src/memory/jobs-worker.ts +56 -9
  329. package/src/memory/lifecycle-events-store.ts +4 -2
  330. package/src/memory/llm-request-log-store.ts +40 -2
  331. package/src/memory/llm-usage-store.ts +4 -3
  332. package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +71 -0
  333. package/src/memory/migrations/200-usage-llm-call-count.ts +20 -0
  334. package/src/memory/migrations/index.ts +2 -0
  335. package/src/memory/query-expansion.ts +83 -0
  336. package/src/memory/retriever.test.ts +119 -0
  337. package/src/memory/retriever.ts +513 -105
  338. package/src/memory/schema/guardian.ts +4 -0
  339. package/src/memory/schema/infrastructure.ts +1 -0
  340. package/src/memory/search/formatting.test.ts +140 -0
  341. package/src/memory/search/formatting.ts +143 -198
  342. package/src/memory/search/mmr.ts +136 -0
  343. package/src/memory/search/staleness.ts +0 -15
  344. package/src/memory/search/tier-classifier.ts +10 -21
  345. package/src/memory/search/types.ts +17 -0
  346. package/src/messaging/providers/slack/adapter.ts +51 -5
  347. package/src/notifications/broadcaster.ts +13 -0
  348. package/src/notifications/copy-composer.ts +8 -0
  349. package/src/oauth/connect-orchestrator.ts +1 -1
  350. package/src/oauth/connection-resolver.ts +2 -2
  351. package/src/oauth/provider-serializer.ts +116 -0
  352. package/src/permissions/trust-store.ts +24 -7
  353. package/src/prompts/__tests__/build-cli-reference-section.test.ts +5 -0
  354. package/src/prompts/journal-context.ts +50 -35
  355. package/src/prompts/persona-resolver.ts +1 -1
  356. package/src/prompts/system-prompt.ts +27 -28
  357. package/src/prompts/templates/BOOTSTRAP.md +14 -1
  358. package/src/prompts/templates/HEARTBEAT.md +10 -0
  359. package/src/prompts/templates/NOW.md +19 -25
  360. package/src/prompts/templates/SOUL.md +13 -1
  361. package/src/prompts/templates/UPDATES.md +12 -0
  362. package/src/prompts/update-bulletin.ts +1 -1
  363. package/src/providers/anthropic/client.ts +89 -18
  364. package/src/providers/model-catalog.ts +22 -2
  365. package/src/providers/model-intents.ts +2 -2
  366. package/src/providers/openai/client.ts +40 -1
  367. package/src/providers/retry.ts +23 -4
  368. package/src/providers/types.ts +2 -0
  369. package/src/runtime/assistant-scope.ts +1 -1
  370. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -0
  371. package/src/runtime/auth/route-policy.ts +1 -0
  372. package/src/runtime/auth/token-service.ts +51 -29
  373. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -1
  374. package/src/runtime/guardian-decision-types.ts +16 -10
  375. package/src/runtime/http-server.ts +3 -14
  376. package/src/runtime/http-types.ts +1 -0
  377. package/src/runtime/migrations/vbundle-builder.ts +7 -4
  378. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -4
  379. package/src/runtime/migrations/vbundle-importer.ts +1 -1
  380. package/src/runtime/routes/conversation-query-routes.ts +40 -8
  381. package/src/runtime/routes/conversation-routes.ts +125 -3
  382. package/src/runtime/routes/guardian-action-routes.ts +9 -3
  383. package/src/runtime/routes/identity-routes.ts +25 -4
  384. package/src/runtime/routes/llm-context-normalization.ts +1 -0
  385. package/src/runtime/routes/log-export-routes.ts +34 -12
  386. package/src/runtime/routes/migration-routes.ts +6 -10
  387. package/src/runtime/routes/oauth-apps.ts +2 -9
  388. package/src/runtime/routes/oauth-providers.ts +60 -0
  389. package/src/runtime/routes/pairing-routes.ts +0 -8
  390. package/src/runtime/routes/settings-routes.ts +0 -1
  391. package/src/runtime/routes/telemetry-routes.ts +16 -4
  392. package/src/security/encrypted-store.ts +2 -2
  393. package/src/security/secret-allowlist.ts +3 -3
  394. package/src/signals/emit-event.ts +42 -0
  395. package/src/signals/user-message.ts +37 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +83 -19
  397. package/src/telemetry/usage-telemetry-reporter.ts +23 -17
  398. package/src/tools/browser/runtime-check.ts +2 -2
  399. package/src/tools/credentials/vault.ts +2 -249
  400. package/src/tools/memory/definitions.ts +1 -1
  401. package/src/tools/memory/handlers.test.ts +50 -8
  402. package/src/tools/memory/handlers.ts +3 -1
  403. package/src/tools/side-effects.ts +1 -6
  404. package/src/tools/terminal/safe-env.ts +3 -2
  405. package/src/tools/terminal/shell.ts +11 -14
  406. package/src/tools/tool-approval-handler.ts +20 -1
  407. package/src/tools/tool-input-summary.ts +66 -0
  408. package/src/tools/types.ts +4 -0
  409. package/src/usage/types.ts +4 -0
  410. package/src/util/device-id.ts +10 -10
  411. package/src/util/platform.ts +71 -33
  412. package/src/util/pricing.ts +19 -6
  413. package/src/util/strip-comment-lines.ts +28 -0
  414. package/src/workspace/git-service.ts +8 -18
  415. package/src/workspace/migrations/003-seed-device-id.ts +6 -4
  416. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +7 -1
  417. package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -4
  418. package/src/workspace/migrations/021-move-signals-to-workspace.ts +84 -0
  419. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +94 -0
  420. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +86 -0
  421. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +126 -0
  422. package/src/workspace/migrations/migrate-to-workspace-volume.ts +3 -6
  423. package/src/workspace/migrations/registry.ts +8 -0
  424. package/src/signals/confirm.ts +0 -82
  425. package/src/signals/trust-rule.ts +0 -174
@@ -1,5 +1,4 @@
1
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
1
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
2
  import { join } from "node:path";
4
3
  import {
5
4
  afterAll,
@@ -11,22 +10,7 @@ import {
11
10
  test,
12
11
  } from "bun:test";
13
12
 
14
- const testDir = mkdtempSync(join(tmpdir(), "memory-regressions-"));
15
-
16
- const testWorkspaceDir = join(testDir, ".vellum", "workspace");
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
- getWorkspaceDir: () => testWorkspaceDir,
28
- getWorkspacePromptPath: (file: string) => join(testWorkspaceDir, file),
29
- }));
13
+ const testWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR!;
30
14
 
31
15
  mock.module("../util/logger.js", () => ({
32
16
  getLogger: () =>
@@ -129,6 +113,7 @@ import {
129
113
  formatAbsoluteTime,
130
114
  formatRelativeTime,
131
115
  injectMemoryRecallAsUserBlock,
116
+ lookupSupersessionChain,
132
117
  } from "../memory/retriever.js";
133
118
  import {
134
119
  conversations,
@@ -140,6 +125,7 @@ import {
140
125
  memorySummaries,
141
126
  messages,
142
127
  } from "../memory/schema.js";
128
+ import { buildMemoryInjection } from "../memory/search/formatting.js";
143
129
  import { buildCoreIdentityContext } from "../prompts/system-prompt.js";
144
130
  import type { Message } from "../providers/types.js";
145
131
 
@@ -167,11 +153,6 @@ describe("Memory regressions", () => {
167
153
 
168
154
  afterAll(() => {
169
155
  resetDb();
170
- try {
171
- rmSync(testDir, { recursive: true });
172
- } catch {
173
- // best effort cleanup
174
- }
175
156
  });
176
157
 
177
158
  function semanticRecallConfig() {
@@ -216,7 +197,10 @@ describe("Memory regressions", () => {
216
197
  conversationId: "conv-baseline-scope",
217
198
  role: "user",
218
199
  content: JSON.stringify([
219
- { type: "text", text: "The user likes dark mode." },
200
+ {
201
+ type: "text",
202
+ text: "The user strongly prefers dark mode for all editor themes and UIs.",
203
+ },
220
204
  ]),
221
205
  createdAt: now,
222
206
  })
@@ -229,7 +213,10 @@ describe("Memory regressions", () => {
229
213
  conversationId: "conv-baseline-scope",
230
214
  role: "user",
231
215
  content: JSON.stringify([
232
- { type: "text", text: "The user likes dark mode." },
216
+ {
217
+ type: "text",
218
+ text: "The user strongly prefers dark mode for all editor themes and UIs.",
219
+ },
233
220
  ]),
234
221
  createdAt: now,
235
222
  },
@@ -1571,7 +1558,10 @@ describe("Memory regressions", () => {
1571
1558
  conversationId: "conv-scope-test",
1572
1559
  role: "user",
1573
1560
  content: JSON.stringify([
1574
- { type: "text", text: "Remember my scope preference" },
1561
+ {
1562
+ type: "text",
1563
+ text: "Remember my scope preference for organizing projects by team and priority level.",
1564
+ },
1575
1565
  ]),
1576
1566
  createdAt: now,
1577
1567
  })
@@ -1583,7 +1573,10 @@ describe("Memory regressions", () => {
1583
1573
  conversationId: "conv-scope-test",
1584
1574
  role: "user",
1585
1575
  content: JSON.stringify([
1586
- { type: "text", text: "Remember my scope preference" },
1576
+ {
1577
+ type: "text",
1578
+ text: "Remember my scope preference for organizing projects by team and priority level.",
1579
+ },
1587
1580
  ]),
1588
1581
  createdAt: now,
1589
1582
  scopeId: "project-xyz",
@@ -2278,11 +2271,13 @@ describe("Memory regressions", () => {
2278
2271
  }
2279
2272
  });
2280
2273
 
2281
- // PR-18: extract_items jobs carry scopeId through the async pipeline
2282
- test("extract_items job payload includes scopeId from private conversation", async () => {
2274
+ // PR-18: batch_extract jobs carry scopeId through the async pipeline
2275
+ test("batch_extract job payload includes scopeId from private conversation", async () => {
2283
2276
  // These tests verify job payload contents, so LLM extraction must be
2284
- // enabled — otherwise the indexer skips enqueuing extract_items entirely.
2277
+ // enabled — otherwise the indexer skips enqueuing batch_extract entirely.
2278
+ // Set batchSize=1 so a single message triggers immediate batch_extract.
2285
2279
  TEST_CONFIG.memory.extraction.useLLM = true;
2280
+ TEST_CONFIG.memory.extraction.batchSize = 1;
2286
2281
  try {
2287
2282
  const conv = createConversation({
2288
2283
  title: "Private scope job test",
@@ -2300,8 +2295,13 @@ describe("Memory regressions", () => {
2300
2295
  const extractJobs = db
2301
2296
  .select()
2302
2297
  .from(memoryJobs)
2303
- .where(eq(memoryJobs.type, "extract_items"))
2304
- .all();
2298
+ .where(eq(memoryJobs.type, "batch_extract"))
2299
+ .all()
2300
+ .filter(
2301
+ (j) =>
2302
+ JSON.parse(j.payload).conversationId === conv.id &&
2303
+ JSON.parse(j.payload).scopeId != null,
2304
+ );
2305
2305
 
2306
2306
  expect(extractJobs.length).toBeGreaterThan(0);
2307
2307
  const lastJob = extractJobs[extractJobs.length - 1];
@@ -2309,11 +2309,13 @@ describe("Memory regressions", () => {
2309
2309
  expect(payload.scopeId).toBe(conv.memoryScopeId);
2310
2310
  } finally {
2311
2311
  TEST_CONFIG.memory.extraction.useLLM = false;
2312
+ TEST_CONFIG.memory.extraction.batchSize = 10;
2312
2313
  }
2313
2314
  });
2314
2315
 
2315
- test("extract_items job payload defaults scopeId to default for standard conversations", async () => {
2316
+ test("batch_extract job payload defaults scopeId to default for standard conversations", async () => {
2316
2317
  TEST_CONFIG.memory.extraction.useLLM = true;
2318
+ TEST_CONFIG.memory.extraction.batchSize = 1;
2317
2319
  try {
2318
2320
  const conv = createConversation({
2319
2321
  title: "Standard scope job test",
@@ -2331,8 +2333,13 @@ describe("Memory regressions", () => {
2331
2333
  const extractJobs = db
2332
2334
  .select()
2333
2335
  .from(memoryJobs)
2334
- .where(eq(memoryJobs.type, "extract_items"))
2335
- .all();
2336
+ .where(eq(memoryJobs.type, "batch_extract"))
2337
+ .all()
2338
+ .filter(
2339
+ (j) =>
2340
+ JSON.parse(j.payload).conversationId === conv.id &&
2341
+ JSON.parse(j.payload).scopeId != null,
2342
+ );
2336
2343
 
2337
2344
  expect(extractJobs.length).toBeGreaterThan(0);
2338
2345
  const lastJob = extractJobs[extractJobs.length - 1];
@@ -2340,6 +2347,7 @@ describe("Memory regressions", () => {
2340
2347
  expect(payload.scopeId).toBe("default");
2341
2348
  } finally {
2342
2349
  TEST_CONFIG.memory.extraction.useLLM = false;
2350
+ TEST_CONFIG.memory.extraction.batchSize = 10;
2343
2351
  }
2344
2352
  });
2345
2353
 
@@ -3209,7 +3217,7 @@ describe("Memory regressions", () => {
3209
3217
 
3210
3218
  // ── Trust-aware extraction gating tests (M3) ───────────────────────
3211
3219
 
3212
- test("untrusted actor messages do not enqueue extract_items", async () => {
3220
+ test("untrusted actor messages do not enqueue batch_extract", async () => {
3213
3221
  const db = getDb();
3214
3222
  const now = Date.now();
3215
3223
  db.insert(conversations)
@@ -3232,7 +3240,10 @@ describe("Memory regressions", () => {
3232
3240
  conversationId: "conv-untrusted-gate",
3233
3241
  role: "user",
3234
3242
  content: JSON.stringify([
3235
- { type: "text", text: "Untrusted user preference for dark mode." },
3243
+ {
3244
+ type: "text",
3245
+ text: "Untrusted user preference for dark mode across all editor themes and interfaces.",
3246
+ },
3236
3247
  ]),
3237
3248
  createdAt: now,
3238
3249
  })
@@ -3244,7 +3255,10 @@ describe("Memory regressions", () => {
3244
3255
  conversationId: "conv-untrusted-gate",
3245
3256
  role: "user",
3246
3257
  content: JSON.stringify([
3247
- { type: "text", text: "Untrusted user preference for dark mode." },
3258
+ {
3259
+ type: "text",
3260
+ text: "Untrusted user preference for dark mode across all editor themes and interfaces.",
3261
+ },
3248
3262
  ]),
3249
3263
  createdAt: now,
3250
3264
  provenanceTrustClass: "trusted_contact",
@@ -3254,18 +3268,19 @@ describe("Memory regressions", () => {
3254
3268
 
3255
3269
  expect(result.indexedSegments).toBeGreaterThan(0);
3256
3270
 
3257
- // No extract_items jobs should be enqueued
3271
+ // No batch_extract jobs should be enqueued for untrusted actors
3258
3272
  const extractJobs = db
3259
3273
  .select()
3260
3274
  .from(memoryJobs)
3261
- .where(and(eq(memoryJobs.type, "extract_items")))
3275
+ .where(eq(memoryJobs.type, "batch_extract"))
3262
3276
  .all()
3263
- .filter((j) => JSON.parse(j.payload).messageId === "msg-untrusted-gate");
3277
+ .filter(
3278
+ (j) => JSON.parse(j.payload).conversationId === "conv-untrusted-gate",
3279
+ );
3264
3280
  expect(extractJobs.length).toBe(0);
3265
3281
 
3266
- // enqueuedJobs should reflect: embed jobs + summary (1), no extract (0)
3267
- const expectedJobs = result.indexedSegments + 1; // embed per segment + summary
3268
- expect(result.enqueuedJobs).toBe(expectedJobs);
3282
+ // enqueuedJobs reflects embed jobs only (no extraction for untrusted)
3283
+ expect(result.enqueuedJobs).toBe(result.indexedSegments);
3269
3284
  });
3270
3285
 
3271
3286
  test("trusted guardian messages still enqueue extraction", async () => {
@@ -3291,7 +3306,10 @@ describe("Memory regressions", () => {
3291
3306
  conversationId: "conv-trusted-gate",
3292
3307
  role: "user",
3293
3308
  content: JSON.stringify([
3294
- { type: "text", text: "Trusted guardian preference for light mode." },
3309
+ {
3310
+ type: "text",
3311
+ text: "Trusted guardian preference for light mode with high contrast accessibility settings.",
3312
+ },
3295
3313
  ]),
3296
3314
  createdAt: now,
3297
3315
  })
@@ -3303,7 +3321,10 @@ describe("Memory regressions", () => {
3303
3321
  conversationId: "conv-trusted-gate",
3304
3322
  role: "user",
3305
3323
  content: JSON.stringify([
3306
- { type: "text", text: "Trusted guardian preference for light mode." },
3324
+ {
3325
+ type: "text",
3326
+ text: "Trusted guardian preference for light mode with high contrast accessibility settings.",
3327
+ },
3307
3328
  ]),
3308
3329
  createdAt: now,
3309
3330
  provenanceTrustClass: "guardian",
@@ -3313,18 +3334,16 @@ describe("Memory regressions", () => {
3313
3334
 
3314
3335
  expect(result.indexedSegments).toBeGreaterThan(0);
3315
3336
 
3316
- // extract_items job should be enqueued for trusted guardian
3337
+ // batch_extract job should be enqueued (debounced) for trusted guardian
3317
3338
  const extractJobs = db
3318
3339
  .select()
3319
3340
  .from(memoryJobs)
3320
- .where(eq(memoryJobs.type, "extract_items"))
3341
+ .where(eq(memoryJobs.type, "batch_extract"))
3321
3342
  .all()
3322
- .filter((j) => JSON.parse(j.payload).messageId === "msg-trusted-gate");
3343
+ .filter(
3344
+ (j) => JSON.parse(j.payload).conversationId === "conv-trusted-gate",
3345
+ );
3323
3346
  expect(extractJobs.length).toBe(1);
3324
-
3325
- // enqueuedJobs: embed per segment + extract_items (counts as 2: extract + summary)
3326
- // For user role: shouldExtract=true
3327
- expect(result.enqueuedJobs).toBeGreaterThan(result.indexedSegments + 1);
3328
3347
  });
3329
3348
 
3330
3349
  test("legacy messages without provenance still enqueue extraction", async () => {
@@ -3350,7 +3369,10 @@ describe("Memory regressions", () => {
3350
3369
  conversationId: "conv-legacy-gate",
3351
3370
  role: "user",
3352
3371
  content: JSON.stringify([
3353
- { type: "text", text: "Legacy message with no provenance info." },
3372
+ {
3373
+ type: "text",
3374
+ text: "Legacy message with no provenance info that still needs full extraction processing.",
3375
+ },
3354
3376
  ]),
3355
3377
  createdAt: now,
3356
3378
  })
@@ -3362,7 +3384,10 @@ describe("Memory regressions", () => {
3362
3384
  conversationId: "conv-legacy-gate",
3363
3385
  role: "user",
3364
3386
  content: JSON.stringify([
3365
- { type: "text", text: "Legacy message with no provenance info." },
3387
+ {
3388
+ type: "text",
3389
+ text: "Legacy message with no provenance info that still needs full extraction processing.",
3390
+ },
3366
3391
  ]),
3367
3392
  createdAt: now,
3368
3393
  // provenanceTrustClass is intentionally omitted (undefined) to test default behavior
@@ -3372,20 +3397,19 @@ describe("Memory regressions", () => {
3372
3397
 
3373
3398
  expect(result.indexedSegments).toBeGreaterThan(0);
3374
3399
 
3375
- // extract_items job should still be enqueued for messages without provenance
3400
+ // batch_extract job should still be enqueued (debounced) for messages without provenance
3376
3401
  const extractJobs = db
3377
3402
  .select()
3378
3403
  .from(memoryJobs)
3379
- .where(eq(memoryJobs.type, "extract_items"))
3404
+ .where(eq(memoryJobs.type, "batch_extract"))
3380
3405
  .all()
3381
- .filter((j) => JSON.parse(j.payload).messageId === "msg-legacy-gate");
3406
+ .filter(
3407
+ (j) => JSON.parse(j.payload).conversationId === "conv-legacy-gate",
3408
+ );
3382
3409
  expect(extractJobs.length).toBe(1);
3383
-
3384
- // enqueuedJobs should include extraction jobs
3385
- expect(result.enqueuedJobs).toBeGreaterThan(result.indexedSegments + 1);
3386
3410
  });
3387
3411
 
3388
- test("unverified_channel messages do not enqueue extract_items", async () => {
3412
+ test("unverified_channel messages do not enqueue batch_extract", async () => {
3389
3413
  const db = getDb();
3390
3414
  const now = Date.now();
3391
3415
  db.insert(conversations)
@@ -3410,7 +3434,7 @@ describe("Memory regressions", () => {
3410
3434
  content: JSON.stringify([
3411
3435
  {
3412
3436
  type: "text",
3413
- text: "Unverified channel preference for compact layout.",
3437
+ text: "Unverified channel preference for compact layout with sidebar navigation always visible.",
3414
3438
  },
3415
3439
  ]),
3416
3440
  createdAt: now,
@@ -3425,7 +3449,7 @@ describe("Memory regressions", () => {
3425
3449
  content: JSON.stringify([
3426
3450
  {
3427
3451
  type: "text",
3428
- text: "Unverified channel preference for compact layout.",
3452
+ text: "Unverified channel preference for compact layout with sidebar navigation always visible.",
3429
3453
  },
3430
3454
  ]),
3431
3455
  createdAt: now,
@@ -3436,18 +3460,19 @@ describe("Memory regressions", () => {
3436
3460
 
3437
3461
  expect(result.indexedSegments).toBeGreaterThan(0);
3438
3462
 
3439
- // No extract_items jobs should be enqueued for unverified channel
3463
+ // No batch_extract jobs should be enqueued for unverified channel
3440
3464
  const extractJobs = db
3441
3465
  .select()
3442
3466
  .from(memoryJobs)
3443
- .where(eq(memoryJobs.type, "extract_items"))
3467
+ .where(eq(memoryJobs.type, "batch_extract"))
3444
3468
  .all()
3445
- .filter((j) => JSON.parse(j.payload).messageId === "msg-unverified-gate");
3469
+ .filter(
3470
+ (j) => JSON.parse(j.payload).conversationId === "conv-unverified-gate",
3471
+ );
3446
3472
  expect(extractJobs.length).toBe(0);
3447
3473
 
3448
- // enqueuedJobs should reflect: embed jobs + summary (1), no extract (0)
3449
- const expectedJobs = result.indexedSegments + 1; // embed per segment + summary
3450
- expect(result.enqueuedJobs).toBe(expectedJobs);
3474
+ // enqueuedJobs reflects embed jobs only (no extraction for untrusted)
3475
+ expect(result.enqueuedJobs).toBe(result.indexedSegments);
3451
3476
  });
3452
3477
 
3453
3478
  test("buildCoreIdentityContext includes identity files when they exist", () => {
@@ -3481,4 +3506,191 @@ describe("Memory regressions", () => {
3481
3506
  const context = buildCoreIdentityContext();
3482
3507
  expect(context).toBeNull();
3483
3508
  });
3509
+
3510
+ // ── Inline supersession rendering tests ──────────────────────────
3511
+
3512
+ test("buildMemoryInjection renders inline supersedes tag for items with supersession chain", () => {
3513
+ const db = getDb();
3514
+ const now = Date.now();
3515
+
3516
+ // Create the superseded (predecessor) item in the DB
3517
+ db.insert(memoryItems)
3518
+ .values({
3519
+ id: "item-predecessor-render",
3520
+ kind: "preference",
3521
+ subject: "color",
3522
+ statement: "Favorite color is blue",
3523
+ status: "active",
3524
+ confidence: 0.9,
3525
+ importance: 0.7,
3526
+ fingerprint: "fp-pred-render",
3527
+ firstSeenAt: now - 86_400_000,
3528
+ lastSeenAt: now - 86_400_000,
3529
+ accessCount: 1,
3530
+ verificationState: "assistant_inferred",
3531
+ })
3532
+ .run();
3533
+
3534
+ const candidate = {
3535
+ key: "item:item-superseding-render",
3536
+ type: "item" as const,
3537
+ id: "item-superseding-render",
3538
+ source: "semantic" as const,
3539
+ text: "Favorite color is green",
3540
+ kind: "preference",
3541
+ confidence: 0.9,
3542
+ importance: 0.8,
3543
+ createdAt: now,
3544
+ semantic: 0.9,
3545
+ recency: 0.8,
3546
+ finalScore: 0.85,
3547
+ supersedes: "item-predecessor-render",
3548
+ };
3549
+
3550
+ const injection = buildMemoryInjection({
3551
+ candidates: [candidate],
3552
+ totalBudgetTokens: 5000,
3553
+ });
3554
+
3555
+ expect(injection).toContain("<supersedes count=");
3556
+ expect(injection).toContain('count="1"');
3557
+ expect(injection).toContain("Favorite color is blue");
3558
+ expect(injection).toContain("</supersedes>");
3559
+ // The supersedes tag should be inside the item tag
3560
+ expect(injection).toMatch(
3561
+ /<item[^>]*>.*<supersedes.*<\/supersedes><\/item>/,
3562
+ );
3563
+
3564
+ // Clean up
3565
+ db.delete(memoryItems)
3566
+ .where(eq(memoryItems.id, "item-predecessor-render"))
3567
+ .run();
3568
+ });
3569
+
3570
+ test("lookupSupersessionChain counts chain depth correctly", () => {
3571
+ const db = getDb();
3572
+ const now = Date.now();
3573
+ const MS_PER_DAY = 86_400_000;
3574
+
3575
+ // Create a chain of 3 items: grandparent → parent → child
3576
+ db.insert(memoryItems)
3577
+ .values({
3578
+ id: "item-chain-grandparent",
3579
+ kind: "fact",
3580
+ subject: "address",
3581
+ statement: "Lives at 123 Main St",
3582
+ status: "active",
3583
+ confidence: 0.8,
3584
+ importance: 0.6,
3585
+ fingerprint: "fp-chain-gp",
3586
+ firstSeenAt: now - 3 * MS_PER_DAY,
3587
+ lastSeenAt: now - 3 * MS_PER_DAY,
3588
+ accessCount: 1,
3589
+ verificationState: "assistant_inferred",
3590
+ })
3591
+ .run();
3592
+
3593
+ db.insert(memoryItems)
3594
+ .values({
3595
+ id: "item-chain-parent",
3596
+ kind: "fact",
3597
+ subject: "address",
3598
+ statement: "Lives at 456 Oak Ave",
3599
+ status: "active",
3600
+ confidence: 0.8,
3601
+ importance: 0.6,
3602
+ fingerprint: "fp-chain-p",
3603
+ supersedes: "item-chain-grandparent",
3604
+ firstSeenAt: now - 2 * MS_PER_DAY,
3605
+ lastSeenAt: now - 2 * MS_PER_DAY,
3606
+ accessCount: 1,
3607
+ verificationState: "assistant_inferred",
3608
+ })
3609
+ .run();
3610
+
3611
+ db.insert(memoryItems)
3612
+ .values({
3613
+ id: "item-chain-child",
3614
+ kind: "fact",
3615
+ subject: "address",
3616
+ statement: "Lives at 789 Pine Blvd",
3617
+ status: "active",
3618
+ confidence: 0.9,
3619
+ importance: 0.7,
3620
+ fingerprint: "fp-chain-c",
3621
+ supersedes: "item-chain-parent",
3622
+ firstSeenAt: now - 1 * MS_PER_DAY,
3623
+ lastSeenAt: now - 1 * MS_PER_DAY,
3624
+ accessCount: 1,
3625
+ verificationState: "assistant_inferred",
3626
+ })
3627
+ .run();
3628
+
3629
+ // Look up from the child's perspective (supersedes parent)
3630
+ const result = lookupSupersessionChain("item-chain-parent");
3631
+ expect(result).not.toBeNull();
3632
+ expect(result!.previousStatement).toBe("Lives at 456 Oak Ave");
3633
+ expect(result!.previousTimestamp).toBe(now - 2 * MS_PER_DAY);
3634
+ // Chain: parent → grandparent = depth 2
3635
+ expect(result!.chainDepth).toBe(2);
3636
+
3637
+ // Look up direct predecessor (grandparent has no supersedes)
3638
+ const gpResult = lookupSupersessionChain("item-chain-grandparent");
3639
+ expect(gpResult).not.toBeNull();
3640
+ expect(gpResult!.previousStatement).toBe("Lives at 123 Main St");
3641
+ expect(gpResult!.chainDepth).toBe(1);
3642
+
3643
+ // Non-existent ID returns null
3644
+ const nullResult = lookupSupersessionChain("item-nonexistent");
3645
+ expect(nullResult).toBeNull();
3646
+
3647
+ // Clean up
3648
+ db.delete(memoryItems).where(eq(memoryItems.id, "item-chain-child")).run();
3649
+ db.delete(memoryItems).where(eq(memoryItems.id, "item-chain-parent")).run();
3650
+ db.delete(memoryItems)
3651
+ .where(eq(memoryItems.id, "item-chain-grandparent"))
3652
+ .run();
3653
+ });
3654
+
3655
+ test("escapeXmlTags escapes memory_context, recalled, and item delimiter tags", () => {
3656
+ // Verify new tag vocabulary is escaped by the existing generic escaper
3657
+ expect(escapeXmlTags("</memory_context>")).toBe("\uFF1C/memory_context>");
3658
+ expect(escapeXmlTags("</recalled>")).toBe("\uFF1C/recalled>");
3659
+ expect(escapeXmlTags("</item>")).toBe("\uFF1C/item>");
3660
+ expect(escapeXmlTags("</segment>")).toBe("\uFF1C/segment>");
3661
+ expect(escapeXmlTags("</supersedes>")).toBe("\uFF1C/supersedes>");
3662
+ expect(escapeXmlTags("</echoes>")).toBe("\uFF1C/echoes>");
3663
+
3664
+ // Opening tags too
3665
+ expect(escapeXmlTags("<memory_context>")).toBe("\uFF1Cmemory_context>");
3666
+ expect(escapeXmlTags("<recalled>")).toBe("\uFF1Crecalled>");
3667
+ expect(escapeXmlTags("<item>")).toBe("\uFF1Citem>");
3668
+ });
3669
+
3670
+ test("buildMemoryInjection renders items without supersedes normally", () => {
3671
+ const now = Date.now();
3672
+ const candidate = {
3673
+ key: "item:item-no-supersedes",
3674
+ type: "item" as const,
3675
+ id: "item-no-supersedes",
3676
+ source: "semantic" as const,
3677
+ text: "User prefers dark mode",
3678
+ kind: "preference",
3679
+ confidence: 0.9,
3680
+ importance: 0.8,
3681
+ createdAt: now,
3682
+ semantic: 0.9,
3683
+ recency: 0.8,
3684
+ finalScore: 0.85,
3685
+ };
3686
+
3687
+ const injection = buildMemoryInjection({
3688
+ candidates: [candidate],
3689
+ totalBudgetTokens: 5000,
3690
+ });
3691
+
3692
+ expect(injection).toContain("User prefers dark mode");
3693
+ expect(injection).not.toContain("<supersedes");
3694
+ expect(injection).not.toContain("</supersedes>");
3695
+ });
3484
3696
  });
@@ -9,9 +9,6 @@
9
9
  * no candidates are found and injectedText is empty. The tests verify
10
10
  * pipeline completion, latency bounds, and token budget enforcement.
11
11
  */
12
- import { mkdtempSync, rmSync } from "node:fs";
13
- import { tmpdir } from "node:os";
14
- import { join } from "node:path";
15
12
  import {
16
13
  afterAll,
17
14
  beforeAll,
@@ -22,19 +19,6 @@ import {
22
19
  test,
23
20
  } from "bun:test";
24
21
 
25
- const testDir = mkdtempSync(join(tmpdir(), "mem-retrieval-bench-"));
26
-
27
- mock.module("../util/platform.js", () => ({
28
- getDataDir: () => testDir,
29
- isMacOS: () => process.platform === "darwin",
30
- isLinux: () => process.platform === "linux",
31
- isWindows: () => process.platform === "win32",
32
- getPidPath: () => join(testDir, "test.pid"),
33
- getDbPath: () => join(testDir, "test.db"),
34
- getLogPath: () => join(testDir, "test.log"),
35
- ensureDataDir: () => {},
36
- }));
37
-
38
22
  mock.module("../util/logger.js", () => ({
39
23
  getLogger: () =>
40
24
  new Proxy({} as Record<string, unknown>, {
@@ -172,11 +156,6 @@ describe("Memory retrieval benchmark", () => {
172
156
 
173
157
  afterAll(() => {
174
158
  resetDb();
175
- try {
176
- rmSync(testDir, { recursive: true });
177
- } catch {
178
- // best effort cleanup
179
- }
180
159
  });
181
160
 
182
161
  test("retrieval completes under 500ms for 100 items", async () => {
@@ -16,26 +16,10 @@
16
16
  * processes and is not tested here.
17
17
  */
18
18
 
19
- import { mkdtempSync, rmSync } from "node:fs";
20
- import { tmpdir } from "node:os";
21
- import { join } from "node:path";
22
19
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
23
20
 
24
21
  import { eq } from "drizzle-orm";
25
22
 
26
- const testDir = mkdtempSync(join(tmpdir(), "memory-upsert-concurrency-"));
27
-
28
- mock.module("../util/platform.js", () => ({
29
- getDataDir: () => testDir,
30
- isMacOS: () => process.platform === "darwin",
31
- isLinux: () => process.platform === "linux",
32
- isWindows: () => process.platform === "win32",
33
- getPidPath: () => join(testDir, "test.pid"),
34
- getDbPath: () => join(testDir, "test.db"),
35
- getLogPath: () => join(testDir, "test.log"),
36
- ensureDataDir: () => {},
37
- }));
38
-
39
23
  mock.module("../util/logger.js", () => ({
40
24
  getLogger: () =>
41
25
  new Proxy({} as Record<string, unknown>, {
@@ -89,11 +73,6 @@ initializeDb();
89
73
 
90
74
  afterAll(() => {
91
75
  resetDb();
92
- try {
93
- rmSync(testDir, { recursive: true });
94
- } catch {
95
- // best effort cleanup
96
- }
97
76
  });
98
77
 
99
78
  function resetTables() {