@vellumai/assistant 0.4.48 → 0.4.50

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 (423) hide show
  1. package/ARCHITECTURE.md +26 -35
  2. package/README.md +5 -26
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/architecture/memory.md +180 -119
  6. package/docs/runbook-trusted-contacts.md +3 -8
  7. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  8. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  9. package/package.json +2 -2
  10. package/src/__tests__/actor-token-service.test.ts +0 -1
  11. package/src/__tests__/agent-loop.test.ts +3 -1
  12. package/src/__tests__/anthropic-provider.test.ts +249 -2
  13. package/src/__tests__/approval-cascade.test.ts +796 -0
  14. package/src/__tests__/approval-primitive.test.ts +0 -1
  15. package/src/__tests__/approval-routes-http.test.ts +4 -0
  16. package/src/__tests__/assistant-attachments.test.ts +12 -34
  17. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  18. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  19. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  22. package/src/__tests__/channel-guardian.test.ts +0 -2
  23. package/src/__tests__/channel-readiness-routes.test.ts +15 -6
  24. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  25. package/src/__tests__/checker.test.ts +13 -20
  26. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  27. package/src/__tests__/computer-use-tools.test.ts +2 -19
  28. package/src/__tests__/config-schema.test.ts +1 -68
  29. package/src/__tests__/config-watcher.test.ts +0 -1
  30. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  31. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  32. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  33. package/src/__tests__/context-token-estimator.test.ts +196 -13
  34. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  35. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  36. package/src/__tests__/conversation-routes-guardian-reply.test.ts +152 -0
  37. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  38. package/src/__tests__/credential-metadata-store.test.ts +64 -73
  39. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  40. package/src/__tests__/credential-security-invariants.test.ts +13 -7
  41. package/src/__tests__/credential-vault-unit.test.ts +284 -49
  42. package/src/__tests__/credential-vault.test.ts +150 -16
  43. package/src/__tests__/credentials-cli.test.ts +71 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  45. package/src/__tests__/date-context.test.ts +93 -77
  46. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  47. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  48. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  49. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  50. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  51. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  52. package/src/__tests__/guardian-routing-invariants.test.ts +93 -1
  53. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  54. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  55. package/src/__tests__/heartbeat-service.test.ts +0 -1
  56. package/src/__tests__/history-repair.test.ts +245 -0
  57. package/src/__tests__/host-cu-proxy.test.ts +791 -0
  58. package/src/__tests__/host-shell-tool.test.ts +27 -15
  59. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  60. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  61. package/src/__tests__/integration-status.test.ts +32 -51
  62. package/src/__tests__/intent-routing.test.ts +0 -1
  63. package/src/__tests__/invite-redemption-service.test.ts +65 -1
  64. package/src/__tests__/invite-routes-http.test.ts +10 -9
  65. package/src/__tests__/keychain-broker-client.test.ts +14 -46
  66. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  67. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  68. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  69. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  70. package/src/__tests__/memory-regressions.test.ts +477 -2841
  71. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  72. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  73. package/src/__tests__/mime-builder.test.ts +28 -0
  74. package/src/__tests__/native-web-search.test.ts +1 -0
  75. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  76. package/src/__tests__/oauth-cli.test.ts +941 -15
  77. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  78. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  79. package/src/__tests__/oauth-store.test.ts +870 -0
  80. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  81. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  82. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  83. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  84. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  85. package/src/__tests__/recording-handler.test.ts +3 -4
  86. package/src/__tests__/registry.test.ts +2 -3
  87. package/src/__tests__/relay-server.test.ts +46 -1
  88. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  89. package/src/__tests__/schedule-store.test.ts +0 -1
  90. package/src/__tests__/schedule-tools.test.ts +32 -0
  91. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  92. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  93. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  94. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  95. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  96. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  97. package/src/__tests__/secure-keys.test.ts +7 -2
  98. package/src/__tests__/send-endpoint-busy.test.ts +24 -6
  99. package/src/__tests__/sequence-store.test.ts +0 -1
  100. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  101. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  102. package/src/__tests__/session-agent-loop.test.ts +19 -15
  103. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  104. package/src/__tests__/session-error.test.ts +124 -2
  105. package/src/__tests__/session-history-web-search.test.ts +918 -0
  106. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  107. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  108. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  109. package/src/__tests__/session-queue.test.ts +37 -27
  110. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  111. package/src/__tests__/session-slash-known.test.ts +1 -15
  112. package/src/__tests__/session-slash-queue.test.ts +1 -15
  113. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  114. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  115. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  116. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  117. package/src/__tests__/skill-include-graph.test.ts +66 -0
  118. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  119. package/src/__tests__/skill-load-tool.test.ts +149 -1
  120. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  121. package/src/__tests__/skills-install-extract.test.ts +93 -0
  122. package/src/__tests__/skills-uninstall.test.ts +1 -1
  123. package/src/__tests__/skills.test.ts +3 -3
  124. package/src/__tests__/skillssh-registry.test.ts +451 -0
  125. package/src/__tests__/slack-channel-config.test.ts +67 -3
  126. package/src/__tests__/slack-share-routes.test.ts +17 -19
  127. package/src/__tests__/system-prompt.test.ts +0 -1
  128. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  129. package/src/__tests__/terminal-tools.test.ts +4 -3
  130. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  131. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  132. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  133. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  134. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  135. package/src/__tests__/tool-executor.test.ts +0 -1
  136. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  137. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  138. package/src/__tests__/trust-store.test.ts +7 -13
  139. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  140. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  141. package/src/__tests__/twilio-routes.test.ts +0 -16
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  143. package/src/__tests__/voice-invite-redemption.test.ts +32 -1
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  145. package/src/agent/ax-tree-compaction.test.ts +286 -0
  146. package/src/agent/loop.ts +104 -131
  147. package/src/approvals/AGENTS.md +1 -1
  148. package/src/approvals/guardian-request-resolvers.ts +14 -2
  149. package/src/bundler/compiler-tools.ts +66 -2
  150. package/src/calls/call-domain.ts +133 -6
  151. package/src/calls/call-store.ts +6 -0
  152. package/src/calls/relay-server.ts +52 -18
  153. package/src/calls/relay-setup-router.ts +17 -1
  154. package/src/calls/twilio-config.ts +3 -8
  155. package/src/calls/twilio-routes.ts +1 -2
  156. package/src/calls/types.ts +3 -1
  157. package/src/calls/voice-ingress-preflight.ts +1 -1
  158. package/src/cli/commands/browser-relay.ts +18 -12
  159. package/src/cli/commands/completions.ts +0 -3
  160. package/src/cli/commands/credentials.ts +101 -15
  161. package/src/cli/commands/doctor.ts +4 -3
  162. package/src/cli/commands/mcp.ts +46 -59
  163. package/src/cli/commands/memory.ts +16 -165
  164. package/src/cli/commands/oauth/apps.ts +284 -0
  165. package/src/cli/commands/oauth/connections.ts +633 -0
  166. package/src/cli/commands/oauth/index.ts +52 -0
  167. package/src/cli/commands/oauth/providers.ts +256 -0
  168. package/src/cli/commands/sessions.ts +5 -2
  169. package/src/cli/commands/skills.ts +177 -339
  170. package/src/cli/http-client.ts +0 -20
  171. package/src/cli/main-screen.tsx +2 -2
  172. package/src/cli/program.ts +6 -11
  173. package/src/cli/reference.ts +1 -3
  174. package/src/cli.ts +4 -10
  175. package/src/config/assistant-feature-flags.ts +0 -3
  176. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  177. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  178. package/src/config/bundled-skills/computer-use/TOOLS.json +23 -5
  179. package/src/config/bundled-skills/computer-use/tools/{computer-use-request-control.ts → computer-use-observe.ts} +1 -5
  180. package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
  181. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
  182. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  183. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  184. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  185. package/src/config/bundled-tool-registry.ts +2 -5
  186. package/src/config/env-registry.ts +14 -83
  187. package/src/config/env.ts +11 -50
  188. package/src/config/feature-flag-registry.json +16 -16
  189. package/src/config/loader.ts +0 -6
  190. package/src/config/schema.ts +4 -13
  191. package/src/config/schemas/memory-lifecycle.ts +0 -9
  192. package/src/config/schemas/memory-processing.ts +0 -180
  193. package/src/config/schemas/memory-retrieval.ts +32 -104
  194. package/src/config/schemas/memory.ts +0 -10
  195. package/src/config/skills.ts +21 -2
  196. package/src/config/types.ts +0 -4
  197. package/src/context/image-dimensions.ts +229 -0
  198. package/src/context/token-estimator.ts +75 -12
  199. package/src/context/window-manager.ts +53 -11
  200. package/src/daemon/assistant-attachments.ts +1 -13
  201. package/src/daemon/config-watcher.ts +61 -3
  202. package/src/daemon/daemon-control.ts +1 -1
  203. package/src/daemon/date-context.ts +114 -31
  204. package/src/daemon/handlers/config-ingress.ts +8 -33
  205. package/src/daemon/handlers/config-slack-channel.ts +49 -46
  206. package/src/daemon/handlers/config-telegram.ts +32 -16
  207. package/src/daemon/handlers/sessions.ts +27 -36
  208. package/src/daemon/handlers/shared.ts +0 -130
  209. package/src/daemon/handlers/skills.ts +20 -1
  210. package/src/daemon/history-repair.ts +72 -8
  211. package/src/daemon/host-cu-proxy.ts +430 -0
  212. package/src/daemon/lifecycle.ts +67 -71
  213. package/src/daemon/mcp-reload-service.ts +2 -2
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/computer-use.ts +1 -129
  216. package/src/daemon/message-types/host-cu.ts +19 -0
  217. package/src/daemon/message-types/memory.ts +4 -16
  218. package/src/daemon/message-types/messages.ts +4 -0
  219. package/src/daemon/message-types/sessions.ts +4 -0
  220. package/src/daemon/server.ts +25 -21
  221. package/src/daemon/session-agent-loop-handlers.ts +40 -0
  222. package/src/daemon/session-agent-loop.ts +334 -48
  223. package/src/daemon/session-attachments.ts +1 -2
  224. package/src/daemon/session-error.ts +89 -6
  225. package/src/daemon/session-history.ts +17 -7
  226. package/src/daemon/session-media-retry.ts +6 -2
  227. package/src/daemon/session-memory.ts +69 -149
  228. package/src/daemon/session-process.ts +10 -1
  229. package/src/daemon/session-runtime-assembly.ts +49 -19
  230. package/src/daemon/session-slash.ts +1 -1
  231. package/src/daemon/session-surfaces.ts +43 -28
  232. package/src/daemon/session-tool-setup.ts +9 -10
  233. package/src/daemon/session.ts +150 -17
  234. package/src/daemon/tool-side-effects.ts +2 -8
  235. package/src/daemon/watch-handler.ts +2 -2
  236. package/src/events/tool-metrics-listener.ts +2 -2
  237. package/src/hooks/manager.ts +1 -4
  238. package/src/inbound/public-ingress-urls.ts +7 -7
  239. package/src/instrument.ts +61 -1
  240. package/src/logfire.ts +16 -5
  241. package/src/memory/admin.ts +2 -191
  242. package/src/memory/canonical-guardian-store.ts +38 -2
  243. package/src/memory/conversation-crud.ts +0 -33
  244. package/src/memory/conversation-key-store.ts +21 -0
  245. package/src/memory/conversation-queries.ts +22 -3
  246. package/src/memory/db-init.ts +32 -0
  247. package/src/memory/embedding-backend.ts +84 -8
  248. package/src/memory/embedding-types.ts +9 -1
  249. package/src/memory/indexer.ts +7 -46
  250. package/src/memory/items-extractor.ts +274 -76
  251. package/src/memory/job-handlers/backfill.ts +2 -127
  252. package/src/memory/job-handlers/cleanup.ts +2 -16
  253. package/src/memory/job-handlers/extraction.ts +2 -138
  254. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  255. package/src/memory/job-handlers/summarization.ts +3 -148
  256. package/src/memory/job-utils.ts +21 -59
  257. package/src/memory/jobs-store.ts +1 -159
  258. package/src/memory/jobs-worker.ts +9 -52
  259. package/src/memory/migrations/104-core-indexes.ts +3 -3
  260. package/src/memory/migrations/149-oauth-tables.ts +62 -0
  261. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  262. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  263. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  264. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  265. package/src/memory/migrations/154-drop-fts.ts +20 -0
  266. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  267. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  268. package/src/memory/migrations/index.ts +8 -0
  269. package/src/memory/qdrant-client.ts +148 -51
  270. package/src/memory/raw-query.ts +1 -1
  271. package/src/memory/retriever.test.ts +294 -273
  272. package/src/memory/retriever.ts +421 -645
  273. package/src/memory/schema/calls.ts +2 -0
  274. package/src/memory/schema/index.ts +1 -0
  275. package/src/memory/schema/memory-core.ts +3 -48
  276. package/src/memory/schema/oauth.ts +67 -0
  277. package/src/memory/search/formatting.ts +263 -176
  278. package/src/memory/search/lexical.ts +1 -254
  279. package/src/memory/search/ranking.ts +0 -455
  280. package/src/memory/search/semantic.ts +100 -14
  281. package/src/memory/search/staleness.ts +47 -0
  282. package/src/memory/search/tier-classifier.ts +21 -0
  283. package/src/memory/search/types.ts +15 -77
  284. package/src/memory/task-memory-cleanup.ts +4 -6
  285. package/src/messaging/provider.ts +4 -4
  286. package/src/messaging/providers/gmail/client.ts +82 -2
  287. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  288. package/src/messaging/providers/gmail/people-client.ts +10 -10
  289. package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
  290. package/src/messaging/providers/whatsapp/adapter.ts +11 -8
  291. package/src/messaging/registry.ts +2 -32
  292. package/src/notifications/copy-composer.ts +0 -5
  293. package/src/notifications/signal.ts +4 -5
  294. package/src/oauth/byo-connection.test.ts +133 -25
  295. package/src/oauth/byo-connection.ts +22 -6
  296. package/src/oauth/connect-orchestrator.ts +113 -57
  297. package/src/oauth/connect-types.ts +17 -23
  298. package/src/oauth/connection-resolver.ts +35 -11
  299. package/src/oauth/connection.ts +1 -1
  300. package/src/oauth/manual-token-connection.ts +104 -0
  301. package/src/oauth/oauth-store.ts +582 -0
  302. package/src/oauth/platform-connection.test.ts +29 -0
  303. package/src/oauth/platform-connection.ts +6 -5
  304. package/src/oauth/provider-behaviors.ts +124 -0
  305. package/src/oauth/scope-policy.ts +9 -2
  306. package/src/oauth/seed-providers.ts +167 -0
  307. package/src/oauth/token-persistence.ts +81 -77
  308. package/src/permissions/checker.ts +3 -3
  309. package/src/permissions/defaults.ts +1 -1
  310. package/src/permissions/prompter.ts +10 -1
  311. package/src/permissions/trust-store.ts +36 -1
  312. package/src/playbooks/playbook-compiler.ts +1 -1
  313. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  314. package/src/prompts/system-prompt.ts +46 -42
  315. package/src/providers/anthropic/client.ts +59 -20
  316. package/src/providers/retry.ts +1 -27
  317. package/src/providers/types.ts +7 -1
  318. package/src/runtime/AGENTS.md +9 -0
  319. package/src/runtime/auth/route-policy.ts +6 -6
  320. package/src/runtime/channel-reply-delivery.ts +0 -40
  321. package/src/runtime/gateway-client.ts +0 -7
  322. package/src/runtime/guardian-reply-router.ts +24 -22
  323. package/src/runtime/http-server.ts +10 -8
  324. package/src/runtime/http-types.ts +2 -2
  325. package/src/runtime/invite-redemption-service.ts +19 -1
  326. package/src/runtime/invite-service.ts +25 -0
  327. package/src/runtime/middleware/twilio-validation.ts +1 -11
  328. package/src/runtime/pending-interactions.ts +14 -12
  329. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  330. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  331. package/src/runtime/routes/conversation-routes.ts +81 -19
  332. package/src/runtime/routes/events-routes.ts +21 -11
  333. package/src/runtime/routes/host-cu-routes.ts +97 -0
  334. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  335. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  336. package/src/runtime/routes/integrations/slack/share.ts +6 -7
  337. package/src/runtime/routes/log-export-routes.ts +126 -8
  338. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  339. package/src/runtime/routes/memory-item-routes.ts +503 -0
  340. package/src/runtime/routes/session-management-routes.ts +3 -3
  341. package/src/runtime/routes/settings-routes.ts +55 -48
  342. package/src/runtime/routes/surface-action-routes.ts +1 -1
  343. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  344. package/src/runtime/routes/watch-routes.ts +128 -0
  345. package/src/runtime/routes/workspace-routes.ts +2 -1
  346. package/src/schedule/integration-status.ts +10 -9
  347. package/src/security/credential-key.ts +0 -156
  348. package/src/security/keychain-broker-client.ts +22 -10
  349. package/src/security/oauth2.ts +1 -1
  350. package/src/security/secure-keys.ts +25 -3
  351. package/src/security/token-manager.ts +137 -64
  352. package/src/skills/catalog-install.ts +414 -0
  353. package/src/skills/include-graph.ts +32 -0
  354. package/src/skills/skillssh-registry.ts +503 -0
  355. package/src/telegram/bot-username.ts +2 -3
  356. package/src/tools/assets/search.ts +5 -1
  357. package/src/tools/browser/network-recorder.ts +1 -1
  358. package/src/tools/browser/network-recording-types.ts +1 -1
  359. package/src/tools/computer-use/definitions.ts +36 -11
  360. package/src/tools/computer-use/registry.ts +5 -6
  361. package/src/tools/credentials/broker.ts +1 -2
  362. package/src/tools/credentials/metadata-store.ts +17 -121
  363. package/src/tools/credentials/vault.ts +92 -167
  364. package/src/tools/memory/definitions.ts +4 -13
  365. package/src/tools/memory/handlers.test.ts +83 -103
  366. package/src/tools/memory/handlers.ts +50 -85
  367. package/src/tools/registry.ts +2 -7
  368. package/src/tools/schedule/create.ts +8 -1
  369. package/src/tools/schedule/update.ts +8 -1
  370. package/src/tools/skills/load.ts +85 -3
  371. package/src/tools/watch/watch-state.ts +0 -12
  372. package/src/util/logger.ts +7 -41
  373. package/src/util/platform.ts +9 -28
  374. package/src/watcher/providers/google-calendar.ts +2 -1
  375. package/src/__tests__/clarification-resolver.test.ts +0 -193
  376. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  377. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  378. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  379. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  380. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  381. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  382. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  383. package/src/__tests__/conflict-policy.test.ts +0 -269
  384. package/src/__tests__/conflict-store.test.ts +0 -372
  385. package/src/__tests__/contradiction-checker.test.ts +0 -361
  386. package/src/__tests__/entity-extractor.test.ts +0 -211
  387. package/src/__tests__/entity-search.test.ts +0 -1117
  388. package/src/__tests__/profile-compiler.test.ts +0 -392
  389. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  390. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  391. package/src/__tests__/session-profile-injection.test.ts +0 -557
  392. package/src/cli/commands/dev.ts +0 -129
  393. package/src/cli/commands/map.ts +0 -391
  394. package/src/cli/commands/oauth.ts +0 -77
  395. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  396. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  397. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  398. package/src/daemon/computer-use-session.ts +0 -1026
  399. package/src/daemon/ride-shotgun-handler.ts +0 -569
  400. package/src/daemon/session-conflict-gate.ts +0 -167
  401. package/src/daemon/session-dynamic-profile.ts +0 -77
  402. package/src/memory/clarification-resolver.ts +0 -417
  403. package/src/memory/conflict-intent.ts +0 -205
  404. package/src/memory/conflict-policy.ts +0 -127
  405. package/src/memory/conflict-store.ts +0 -410
  406. package/src/memory/contradiction-checker.ts +0 -508
  407. package/src/memory/entity-extractor.ts +0 -535
  408. package/src/memory/format-recall.ts +0 -47
  409. package/src/memory/fts-reconciler.ts +0 -165
  410. package/src/memory/job-handlers/conflict.ts +0 -200
  411. package/src/memory/profile-compiler.ts +0 -195
  412. package/src/memory/recall-cache.ts +0 -117
  413. package/src/memory/search/entity.ts +0 -535
  414. package/src/memory/search/query-expansion.test.ts +0 -70
  415. package/src/memory/search/query-expansion.ts +0 -118
  416. package/src/oauth/provider-base-urls.ts +0 -21
  417. package/src/oauth/provider-profiles.ts +0 -192
  418. package/src/prompts/computer-use-prompt.ts +0 -98
  419. package/src/runtime/routes/computer-use-routes.ts +0 -641
  420. package/src/runtime/routes/mcp-routes.ts +0 -20
  421. package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
  422. package/src/runtime/telegram-streaming-delivery.ts +0 -393
  423. package/src/tools/computer-use/request-computer-control.ts +0 -56
@@ -0,0 +1,754 @@
1
+ /**
2
+ * Tests for memory item CRUD HTTP endpoints.
3
+ *
4
+ * Covers: list with filters, get by ID, create + duplicate rejection,
5
+ * update + fingerprint collision, delete + 404.
6
+ */
7
+ import { mkdtempSync, rmSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import {
11
+ afterAll,
12
+ beforeAll,
13
+ beforeEach,
14
+ describe,
15
+ expect,
16
+ mock,
17
+ test,
18
+ } from "bun:test";
19
+
20
+ const testDir = mkdtempSync(join(tmpdir(), "memory-item-routes-test-"));
21
+
22
+ mock.module("../../util/platform.js", () => ({
23
+ getDataDir: () => testDir,
24
+ isMacOS: () => process.platform === "darwin",
25
+ isLinux: () => process.platform === "linux",
26
+ isWindows: () => process.platform === "win32",
27
+ getPidPath: () => join(testDir, "test.pid"),
28
+ getDbPath: () => join(testDir, "test.db"),
29
+ getLogPath: () => join(testDir, "test.log"),
30
+ ensureDataDir: () => {},
31
+ }));
32
+
33
+ mock.module("../../util/logger.js", () => ({
34
+ getLogger: () =>
35
+ new Proxy({} as Record<string, unknown>, {
36
+ get: () => () => {},
37
+ }),
38
+ }));
39
+
40
+ // Stub config loader
41
+ mock.module("../../config/loader.js", () => ({
42
+ loadConfig: () => ({}),
43
+ getConfig: () => ({}),
44
+ invalidateConfigCache: () => {},
45
+ }));
46
+
47
+ import { and, eq } from "drizzle-orm";
48
+
49
+ import { getDb, initializeDb, resetDb } from "../../memory/db.js";
50
+ import {
51
+ memoryEmbeddings,
52
+ memoryItems,
53
+ memoryJobs,
54
+ } from "../../memory/schema.js";
55
+ import type { RouteContext } from "../http-router.js";
56
+ import { memoryItemRouteDefinitions } from "./memory-item-routes.js";
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Helpers
60
+ // ---------------------------------------------------------------------------
61
+
62
+ function getHandler(endpoint: string, method: string) {
63
+ const routes = memoryItemRouteDefinitions();
64
+ const route = routes.find(
65
+ (r) => r.endpoint === endpoint && r.method === method,
66
+ );
67
+ if (!route) throw new Error(`No route: ${method} ${endpoint}`);
68
+ return route.handler;
69
+ }
70
+
71
+ function makeCtx(
72
+ searchParams: Record<string, string> = {},
73
+ params: Record<string, string> = {},
74
+ ): RouteContext {
75
+ const url = new URL("http://localhost/v1/memory-items");
76
+ for (const [k, v] of Object.entries(searchParams)) {
77
+ url.searchParams.set(k, v);
78
+ }
79
+ return {
80
+ url,
81
+ req: new Request(url),
82
+ server: {} as ReturnType<typeof Bun.serve>,
83
+ authContext: {} as never,
84
+ params,
85
+ };
86
+ }
87
+
88
+ function makeJsonCtx(
89
+ endpoint: string,
90
+ method: string,
91
+ body: unknown,
92
+ params: Record<string, string> = {},
93
+ ): RouteContext {
94
+ const url = new URL(`http://localhost/v1/${endpoint}`);
95
+ return {
96
+ url,
97
+ req: new Request(url, {
98
+ method,
99
+ headers: { "Content-Type": "application/json" },
100
+ body: JSON.stringify(body),
101
+ }),
102
+ server: {} as ReturnType<typeof Bun.serve>,
103
+ authContext: {} as never,
104
+ params,
105
+ };
106
+ }
107
+
108
+ function insertItem(opts: {
109
+ id: string;
110
+ kind: string;
111
+ subject: string;
112
+ statement: string;
113
+ status?: string;
114
+ importance?: number;
115
+ firstSeenAt?: number;
116
+ lastSeenAt?: number;
117
+ supersedes?: string;
118
+ supersededBy?: string;
119
+ }) {
120
+ const db = getDb();
121
+ const now = Date.now();
122
+ db.insert(memoryItems)
123
+ .values({
124
+ id: opts.id,
125
+ kind: opts.kind,
126
+ subject: opts.subject,
127
+ statement: opts.statement,
128
+ status: opts.status ?? "active",
129
+ confidence: 0.95,
130
+ importance: opts.importance ?? 0.8,
131
+ fingerprint: `fp-${opts.id}`,
132
+ verificationState: "user_confirmed",
133
+ scopeId: "default",
134
+ firstSeenAt: opts.firstSeenAt ?? now,
135
+ lastSeenAt: opts.lastSeenAt ?? now,
136
+ lastUsedAt: null,
137
+ })
138
+ .run();
139
+
140
+ if (opts.supersedes || opts.supersededBy) {
141
+ const set: Record<string, unknown> = {};
142
+ if (opts.supersedes) set.supersedes = opts.supersedes;
143
+ if (opts.supersededBy) set.supersededBy = opts.supersededBy;
144
+ db.update(memoryItems).set(set).where(eq(memoryItems.id, opts.id)).run();
145
+ }
146
+ }
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Suite
150
+ // ---------------------------------------------------------------------------
151
+
152
+ describe("Memory Item Routes", () => {
153
+ beforeAll(() => {
154
+ initializeDb();
155
+ });
156
+
157
+ beforeEach(() => {
158
+ const db = getDb();
159
+ db.run("DELETE FROM memory_embeddings");
160
+ db.run("DELETE FROM memory_item_sources");
161
+ db.run("DELETE FROM memory_items");
162
+ db.run("DELETE FROM memory_jobs");
163
+ });
164
+
165
+ afterAll(() => {
166
+ resetDb();
167
+ rmSync(testDir, { recursive: true, force: true });
168
+ });
169
+
170
+ // =========================================================================
171
+ // GET /v1/memory-items (list)
172
+ // =========================================================================
173
+
174
+ describe("GET /v1/memory-items", () => {
175
+ const handler = getHandler("memory-items", "GET");
176
+
177
+ test("returns empty list when no items", async () => {
178
+ const ctx = makeCtx();
179
+ const res = await handler(ctx);
180
+ expect(res.status).toBe(200);
181
+ const body = (await res.json()) as { items: unknown[]; total: number };
182
+ expect(body.items).toEqual([]);
183
+ expect(body.total).toBe(0);
184
+ });
185
+
186
+ test("returns all active items by default", async () => {
187
+ insertItem({
188
+ id: "i1",
189
+ kind: "preference",
190
+ subject: "s1",
191
+ statement: "st1",
192
+ });
193
+ insertItem({
194
+ id: "i2",
195
+ kind: "identity",
196
+ subject: "s2",
197
+ statement: "st2",
198
+ status: "deleted",
199
+ });
200
+
201
+ const ctx = makeCtx();
202
+ const res = await handler(ctx);
203
+ expect(res.status).toBe(200);
204
+ const body = (await res.json()) as {
205
+ items: Array<{ id: string }>;
206
+ total: number;
207
+ };
208
+ expect(body.total).toBe(1);
209
+ expect(body.items.length).toBe(1);
210
+ expect(body.items[0].id).toBe("i1");
211
+ });
212
+
213
+ test("returns items of all statuses when status=all", async () => {
214
+ insertItem({
215
+ id: "i1",
216
+ kind: "preference",
217
+ subject: "s1",
218
+ statement: "st1",
219
+ status: "active",
220
+ });
221
+ insertItem({
222
+ id: "i2",
223
+ kind: "identity",
224
+ subject: "s2",
225
+ statement: "st2",
226
+ status: "deleted",
227
+ });
228
+
229
+ const ctx = makeCtx({ status: "all" });
230
+ const res = await handler(ctx);
231
+ expect(res.status).toBe(200);
232
+ const body = (await res.json()) as {
233
+ items: Array<{ id: string }>;
234
+ total: number;
235
+ };
236
+ expect(body.total).toBe(2);
237
+ expect(body.items.length).toBe(2);
238
+ const ids = body.items.map((i) => i.id).sort();
239
+ expect(ids).toEqual(["i1", "i2"]);
240
+ });
241
+
242
+ test("filters by kind", async () => {
243
+ insertItem({
244
+ id: "i1",
245
+ kind: "preference",
246
+ subject: "s1",
247
+ statement: "st1",
248
+ });
249
+ insertItem({
250
+ id: "i2",
251
+ kind: "identity",
252
+ subject: "s2",
253
+ statement: "st2",
254
+ });
255
+
256
+ const ctx = makeCtx({ kind: "preference" });
257
+ const res = await handler(ctx);
258
+ const body = (await res.json()) as {
259
+ items: Array<{ id: string }>;
260
+ total: number;
261
+ };
262
+ expect(body.total).toBe(1);
263
+ expect(body.items[0].id).toBe("i1");
264
+ });
265
+
266
+ test("filters by search on subject and statement", async () => {
267
+ insertItem({
268
+ id: "i1",
269
+ kind: "preference",
270
+ subject: "dark mode",
271
+ statement: "User prefers dark mode",
272
+ });
273
+ insertItem({
274
+ id: "i2",
275
+ kind: "identity",
276
+ subject: "name",
277
+ statement: "User name is Alice",
278
+ });
279
+
280
+ const ctx = makeCtx({ search: "dark" });
281
+ const res = await handler(ctx);
282
+ const body = (await res.json()) as {
283
+ items: Array<{ id: string }>;
284
+ total: number;
285
+ };
286
+ expect(body.total).toBe(1);
287
+ expect(body.items[0].id).toBe("i1");
288
+ });
289
+
290
+ test("supports pagination with limit and offset", async () => {
291
+ insertItem({
292
+ id: "i1",
293
+ kind: "preference",
294
+ subject: "s1",
295
+ statement: "st1",
296
+ lastSeenAt: 1000,
297
+ });
298
+ insertItem({
299
+ id: "i2",
300
+ kind: "preference",
301
+ subject: "s2",
302
+ statement: "st2",
303
+ lastSeenAt: 2000,
304
+ });
305
+ insertItem({
306
+ id: "i3",
307
+ kind: "preference",
308
+ subject: "s3",
309
+ statement: "st3",
310
+ lastSeenAt: 3000,
311
+ });
312
+
313
+ const ctx = makeCtx({ limit: "1", offset: "1" });
314
+ const res = await handler(ctx);
315
+ const body = (await res.json()) as {
316
+ items: Array<{ id: string }>;
317
+ total: number;
318
+ };
319
+ expect(body.total).toBe(3);
320
+ expect(body.items.length).toBe(1);
321
+ // Default sort is lastSeenAt desc, so offset 1 should be i2
322
+ expect(body.items[0].id).toBe("i2");
323
+ });
324
+
325
+ test("supports sort by firstSeenAt ascending", async () => {
326
+ insertItem({
327
+ id: "i1",
328
+ kind: "preference",
329
+ subject: "s1",
330
+ statement: "st1",
331
+ firstSeenAt: 3000,
332
+ });
333
+ insertItem({
334
+ id: "i2",
335
+ kind: "preference",
336
+ subject: "s2",
337
+ statement: "st2",
338
+ firstSeenAt: 1000,
339
+ });
340
+
341
+ const ctx = makeCtx({ sort: "firstSeenAt", order: "asc" });
342
+ const res = await handler(ctx);
343
+ const body = (await res.json()) as {
344
+ items: Array<{ id: string }>;
345
+ };
346
+ expect(body.items[0].id).toBe("i2");
347
+ expect(body.items[1].id).toBe("i1");
348
+ });
349
+
350
+ test("rejects invalid kind filter", async () => {
351
+ const ctx = makeCtx({ kind: "bogus" });
352
+ const res = await handler(ctx);
353
+ expect(res.status).toBe(400);
354
+ });
355
+
356
+ test("rejects invalid sort field", async () => {
357
+ const ctx = makeCtx({ sort: "bogus" });
358
+ const res = await handler(ctx);
359
+ expect(res.status).toBe(400);
360
+ });
361
+ });
362
+
363
+ // =========================================================================
364
+ // GET /v1/memory-items/:id
365
+ // =========================================================================
366
+
367
+ describe("GET /v1/memory-items/:id", () => {
368
+ const handler = getHandler("memory-items/:id", "GET");
369
+
370
+ test("returns item by ID", async () => {
371
+ insertItem({
372
+ id: "i1",
373
+ kind: "preference",
374
+ subject: "dark mode",
375
+ statement: "Prefers dark mode",
376
+ });
377
+
378
+ const ctx = makeCtx({}, { id: "i1" });
379
+ const res = await handler(ctx);
380
+ expect(res.status).toBe(200);
381
+ const body = (await res.json()) as {
382
+ item: { id: string; subject: string };
383
+ };
384
+ expect(body.item.id).toBe("i1");
385
+ expect(body.item.subject).toBe("dark mode");
386
+ });
387
+
388
+ test("returns 404 for non-existent item", async () => {
389
+ const ctx = makeCtx({}, { id: "nonexistent" });
390
+ const res = await handler(ctx);
391
+ expect(res.status).toBe(404);
392
+ });
393
+
394
+ test("includes supersedesSubject when supersedes is set", async () => {
395
+ insertItem({
396
+ id: "old",
397
+ kind: "preference",
398
+ subject: "old pref",
399
+ statement: "old",
400
+ });
401
+ insertItem({
402
+ id: "new",
403
+ kind: "preference",
404
+ subject: "new pref",
405
+ statement: "new",
406
+ });
407
+
408
+ // Set supersedes relationship manually
409
+ getDb()
410
+ .update(memoryItems)
411
+ .set({ supersedes: "old" })
412
+ .where(eq(memoryItems.id, "new"))
413
+ .run();
414
+
415
+ const ctx = makeCtx({}, { id: "new" });
416
+ const res = await handler(ctx);
417
+ const body = (await res.json()) as {
418
+ item: { supersedesSubject?: string };
419
+ };
420
+ expect(body.item.supersedesSubject).toBe("old pref");
421
+ });
422
+ });
423
+
424
+ // =========================================================================
425
+ // POST /v1/memory-items
426
+ // =========================================================================
427
+
428
+ describe("POST /v1/memory-items", () => {
429
+ const handler = getHandler("memory-items", "POST");
430
+
431
+ test("creates a new memory item", async () => {
432
+ const ctx = makeJsonCtx("memory-items", "POST", {
433
+ kind: "preference",
434
+ subject: "dark mode",
435
+ statement: "User prefers dark mode",
436
+ });
437
+ const res = await handler(ctx);
438
+ expect(res.status).toBe(201);
439
+ const body = (await res.json()) as {
440
+ item: { id: string; kind: string; subject: string; statement: string };
441
+ };
442
+ expect(body.item.kind).toBe("preference");
443
+ expect(body.item.subject).toBe("dark mode");
444
+ expect(body.item.statement).toBe("User prefers dark mode");
445
+ });
446
+
447
+ test("uses custom importance when provided", async () => {
448
+ const ctx = makeJsonCtx("memory-items", "POST", {
449
+ kind: "preference",
450
+ subject: "importance test",
451
+ statement: "Testing custom importance",
452
+ importance: 0.5,
453
+ });
454
+ const res = await handler(ctx);
455
+ expect(res.status).toBe(201);
456
+ const body = (await res.json()) as {
457
+ item: { importance: number };
458
+ };
459
+ expect(body.item.importance).toBe(0.5);
460
+ });
461
+
462
+ test("rejects duplicate fingerprint", async () => {
463
+ const payload = {
464
+ kind: "preference",
465
+ subject: "dark mode",
466
+ statement: "User prefers dark mode",
467
+ };
468
+ const ctx1 = makeJsonCtx("memory-items", "POST", payload);
469
+ const res1 = await handler(ctx1);
470
+ expect(res1.status).toBe(201);
471
+
472
+ const ctx2 = makeJsonCtx("memory-items", "POST", payload);
473
+ const res2 = await handler(ctx2);
474
+ expect(res2.status).toBe(409);
475
+ });
476
+
477
+ test("rejects invalid kind", async () => {
478
+ const ctx = makeJsonCtx("memory-items", "POST", {
479
+ kind: "bogus",
480
+ subject: "test",
481
+ statement: "test",
482
+ });
483
+ const res = await handler(ctx);
484
+ expect(res.status).toBe(400);
485
+ });
486
+
487
+ test("rejects missing subject", async () => {
488
+ const ctx = makeJsonCtx("memory-items", "POST", {
489
+ kind: "preference",
490
+ statement: "test",
491
+ });
492
+ const res = await handler(ctx);
493
+ expect(res.status).toBe(400);
494
+ });
495
+
496
+ test("rejects missing statement", async () => {
497
+ const ctx = makeJsonCtx("memory-items", "POST", {
498
+ kind: "preference",
499
+ subject: "test",
500
+ });
501
+ const res = await handler(ctx);
502
+ expect(res.status).toBe(400);
503
+ });
504
+
505
+ test("truncates long subject and statement", async () => {
506
+ const longSubject = "a".repeat(200);
507
+ const longStatement = "b".repeat(1000);
508
+ const ctx = makeJsonCtx("memory-items", "POST", {
509
+ kind: "preference",
510
+ subject: longSubject,
511
+ statement: longStatement,
512
+ });
513
+ const res = await handler(ctx);
514
+ expect(res.status).toBe(201);
515
+ const body = (await res.json()) as {
516
+ item: { subject: string; statement: string };
517
+ };
518
+ expect(body.item.subject.length).toBeLessThanOrEqual(80);
519
+ expect(body.item.statement.length).toBeLessThanOrEqual(500);
520
+ });
521
+
522
+ test("enqueues embed job on create", async () => {
523
+ const ctx = makeJsonCtx("memory-items", "POST", {
524
+ kind: "preference",
525
+ subject: "embed test",
526
+ statement: "Should enqueue embed job",
527
+ });
528
+ await handler(ctx);
529
+
530
+ // Verify a memory job was enqueued
531
+ const db = getDb();
532
+ const jobs = db.select().from(memoryJobs).all();
533
+ const embedJobs = jobs.filter(
534
+ (j) => j.type === "embed_item" && j.status === "pending",
535
+ );
536
+ expect(embedJobs.length).toBeGreaterThanOrEqual(1);
537
+ });
538
+ });
539
+
540
+ // =========================================================================
541
+ // PATCH /v1/memory-items/:id
542
+ // =========================================================================
543
+
544
+ describe("PATCH /v1/memory-items/:id", () => {
545
+ const handler = getHandler("memory-items/:id", "PATCH");
546
+
547
+ test("updates subject and statement", async () => {
548
+ insertItem({
549
+ id: "i1",
550
+ kind: "preference",
551
+ subject: "old subject",
552
+ statement: "old statement",
553
+ });
554
+
555
+ const ctx = makeJsonCtx(
556
+ "memory-items/i1",
557
+ "PATCH",
558
+ { subject: "new subject", statement: "new statement" },
559
+ { id: "i1" },
560
+ );
561
+ const res = await handler(ctx);
562
+ expect(res.status).toBe(200);
563
+ const body = (await res.json()) as {
564
+ item: { subject: string; statement: string };
565
+ };
566
+ expect(body.item.subject).toBe("new subject");
567
+ expect(body.item.statement).toBe("new statement");
568
+ });
569
+
570
+ test("returns 404 for non-existent item", async () => {
571
+ const ctx = makeJsonCtx(
572
+ "memory-items/nonexistent",
573
+ "PATCH",
574
+ { subject: "test" },
575
+ { id: "nonexistent" },
576
+ );
577
+ const res = await handler(ctx);
578
+ expect(res.status).toBe(404);
579
+ });
580
+
581
+ test("detects fingerprint collision on update", async () => {
582
+ insertItem({
583
+ id: "i1",
584
+ kind: "preference",
585
+ subject: "first",
586
+ statement: "first statement",
587
+ });
588
+ // Insert a second item using the create handler to get a real fingerprint
589
+ const createHandler = getHandler("memory-items", "POST");
590
+ const createCtx = makeJsonCtx("memory-items", "POST", {
591
+ kind: "preference",
592
+ subject: "second",
593
+ statement: "second statement",
594
+ });
595
+ await createHandler(createCtx);
596
+
597
+ // Now try to update i1 to match the second item's content
598
+ // This should produce the same fingerprint as the second item
599
+ const ctx = makeJsonCtx(
600
+ "memory-items/i1",
601
+ "PATCH",
602
+ { subject: "second", statement: "second statement" },
603
+ { id: "i1" },
604
+ );
605
+ const res = await handler(ctx);
606
+ expect(res.status).toBe(409);
607
+ });
608
+
609
+ test("allows updating kind", async () => {
610
+ insertItem({
611
+ id: "i1",
612
+ kind: "preference",
613
+ subject: "test",
614
+ statement: "test",
615
+ });
616
+
617
+ const ctx = makeJsonCtx(
618
+ "memory-items/i1",
619
+ "PATCH",
620
+ { kind: "identity" },
621
+ { id: "i1" },
622
+ );
623
+ const res = await handler(ctx);
624
+ expect(res.status).toBe(200);
625
+ const body = (await res.json()) as { item: { kind: string } };
626
+ expect(body.item.kind).toBe("identity");
627
+ });
628
+
629
+ test("rejects invalid kind on update", async () => {
630
+ insertItem({
631
+ id: "i1",
632
+ kind: "preference",
633
+ subject: "test",
634
+ statement: "test",
635
+ });
636
+
637
+ const ctx = makeJsonCtx(
638
+ "memory-items/i1",
639
+ "PATCH",
640
+ { kind: "bogus" },
641
+ { id: "i1" },
642
+ );
643
+ const res = await handler(ctx);
644
+ expect(res.status).toBe(400);
645
+ });
646
+
647
+ test("enqueues embed job when statement changes", async () => {
648
+ insertItem({
649
+ id: "i1",
650
+ kind: "preference",
651
+ subject: "test",
652
+ statement: "old statement",
653
+ });
654
+
655
+ // Clear jobs first
656
+ getDb().run("DELETE FROM memory_jobs");
657
+
658
+ const ctx = makeJsonCtx(
659
+ "memory-items/i1",
660
+ "PATCH",
661
+ { statement: "new statement" },
662
+ { id: "i1" },
663
+ );
664
+ await handler(ctx);
665
+
666
+ const db = getDb();
667
+ const jobs = db.select().from(memoryJobs).all();
668
+ const embedJobs = jobs.filter(
669
+ (j) => j.type === "embed_item" && j.status === "pending",
670
+ );
671
+ expect(embedJobs.length).toBe(1);
672
+ });
673
+ });
674
+
675
+ // =========================================================================
676
+ // DELETE /v1/memory-items/:id
677
+ // =========================================================================
678
+
679
+ describe("DELETE /v1/memory-items/:id", () => {
680
+ const handler = getHandler("memory-items/:id", "DELETE");
681
+
682
+ test("deletes item and returns 204", async () => {
683
+ insertItem({
684
+ id: "i1",
685
+ kind: "preference",
686
+ subject: "test",
687
+ statement: "test",
688
+ });
689
+
690
+ const ctx = makeJsonCtx("memory-items/i1", "DELETE", null, { id: "i1" });
691
+ const res = await handler(ctx);
692
+ expect(res.status).toBe(204);
693
+
694
+ // Verify the item is gone
695
+ const db = getDb();
696
+ const item = db
697
+ .select()
698
+ .from(memoryItems)
699
+ .where(eq(memoryItems.id, "i1"))
700
+ .get();
701
+ expect(item).toBeUndefined();
702
+ });
703
+
704
+ test("returns 404 for non-existent item", async () => {
705
+ const ctx = makeJsonCtx("memory-items/nonexistent", "DELETE", null, {
706
+ id: "nonexistent",
707
+ });
708
+ const res = await handler(ctx);
709
+ expect(res.status).toBe(404);
710
+ });
711
+
712
+ test("also deletes associated embeddings", async () => {
713
+ insertItem({
714
+ id: "i1",
715
+ kind: "preference",
716
+ subject: "test",
717
+ statement: "test",
718
+ });
719
+
720
+ // Insert an embedding for this item
721
+ const db = getDb();
722
+ db.insert(memoryEmbeddings)
723
+ .values({
724
+ id: "emb-1",
725
+ targetType: "item",
726
+ targetId: "i1",
727
+ provider: "test",
728
+ model: "test-model",
729
+ dimensions: 384,
730
+ vectorJson: "[]",
731
+ createdAt: Date.now(),
732
+ updatedAt: Date.now(),
733
+ })
734
+ .run();
735
+
736
+ const ctx = makeJsonCtx("memory-items/i1", "DELETE", null, { id: "i1" });
737
+ const res = await handler(ctx);
738
+ expect(res.status).toBe(204);
739
+
740
+ // Verify embedding is also gone
741
+ const emb = db
742
+ .select()
743
+ .from(memoryEmbeddings)
744
+ .where(
745
+ and(
746
+ eq(memoryEmbeddings.targetType, "item"),
747
+ eq(memoryEmbeddings.targetId, "i1"),
748
+ ),
749
+ )
750
+ .get();
751
+ expect(emb).toBeUndefined();
752
+ });
753
+ });
754
+ });