@vellumai/assistant 0.4.49 → 0.4.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/ARCHITECTURE.md +24 -33
  2. package/README.md +3 -3
  3. package/docs/architecture/integrations.md +2 -2
  4. package/docs/architecture/keychain-broker.md +6 -6
  5. package/docs/architecture/memory.md +180 -119
  6. package/knip.json +32 -0
  7. package/package.json +3 -2
  8. package/src/__tests__/agent-loop.test.ts +3 -1
  9. package/src/__tests__/anthropic-provider.test.ts +114 -23
  10. package/src/__tests__/approval-cascade.test.ts +1 -15
  11. package/src/__tests__/approval-routes-http.test.ts +2 -0
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  13. package/src/__tests__/btw-routes.test.ts +61 -5
  14. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  15. package/src/__tests__/checker.test.ts +13 -0
  16. package/src/__tests__/config-schema.test.ts +1 -68
  17. package/src/__tests__/config-watcher.test.ts +8 -0
  18. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  19. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  20. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  21. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  22. package/src/__tests__/credential-security-invariants.test.ts +8 -7
  23. package/src/__tests__/credential-vault-unit.test.ts +23 -18
  24. package/src/__tests__/credential-vault.test.ts +30 -18
  25. package/src/__tests__/credentials-cli.test.ts +257 -82
  26. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  27. package/src/__tests__/date-context.test.ts +93 -77
  28. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  29. package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
  30. package/src/__tests__/history-repair.test.ts +245 -0
  31. package/src/__tests__/host-cu-proxy.test.ts +165 -3
  32. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  33. package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
  34. package/src/__tests__/integration-status.test.ts +31 -30
  35. package/src/__tests__/invite-redemption-service.test.ts +166 -13
  36. package/src/__tests__/invite-routes-http.test.ts +166 -5
  37. package/src/__tests__/keychain-broker-client.test.ts +4 -4
  38. package/src/__tests__/list-messages-attachments.test.ts +193 -0
  39. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  40. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  41. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  42. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  43. package/src/__tests__/memory-regressions.test.ts +477 -2841
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  45. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  46. package/src/__tests__/mime-builder.test.ts +28 -0
  47. package/src/__tests__/native-web-search.test.ts +1 -0
  48. package/src/__tests__/oauth-cli.test.ts +824 -31
  49. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  50. package/src/__tests__/oauth-store.test.ts +363 -17
  51. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  52. package/src/__tests__/registry.test.ts +0 -1
  53. package/src/__tests__/relay-server.test.ts +55 -1
  54. package/src/__tests__/schedule-tools.test.ts +32 -0
  55. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  56. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  57. package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
  58. package/src/__tests__/secure-keys.test.ts +78 -18
  59. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  60. package/src/__tests__/server-history-render.test.ts +2 -2
  61. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  62. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  63. package/src/__tests__/session-agent-loop.test.ts +19 -15
  64. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  65. package/src/__tests__/session-error.test.ts +124 -2
  66. package/src/__tests__/session-history-web-search.test.ts +918 -0
  67. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  68. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  69. package/src/__tests__/session-queue.test.ts +37 -27
  70. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  71. package/src/__tests__/session-slash-known.test.ts +1 -15
  72. package/src/__tests__/session-slash-queue.test.ts +1 -15
  73. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  74. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  75. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  76. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  77. package/src/__tests__/skills-install-extract.test.ts +93 -0
  78. package/src/__tests__/skills.test.ts +2 -2
  79. package/src/__tests__/skillssh-registry.test.ts +451 -0
  80. package/src/__tests__/slack-channel-config.test.ts +10 -8
  81. package/src/__tests__/trust-store.test.ts +15 -0
  82. package/src/__tests__/twilio-config.test.ts +11 -10
  83. package/src/__tests__/twilio-provider.test.ts +9 -4
  84. package/src/__tests__/voice-invite-redemption.test.ts +85 -5
  85. package/src/agent/ax-tree-compaction.test.ts +51 -0
  86. package/src/agent/loop.ts +39 -12
  87. package/src/approvals/AGENTS.md +1 -1
  88. package/src/approvals/guardian-request-resolvers.ts +14 -2
  89. package/src/bundler/compiler-tools.ts +66 -2
  90. package/src/calls/call-domain.ts +134 -3
  91. package/src/calls/call-store.ts +6 -0
  92. package/src/calls/relay-server.ts +44 -6
  93. package/src/calls/relay-setup-router.ts +17 -1
  94. package/src/calls/twilio-config.ts +5 -4
  95. package/src/calls/twilio-provider.ts +14 -9
  96. package/src/calls/twilio-rest.ts +10 -7
  97. package/src/calls/types.ts +3 -1
  98. package/src/cli/commands/config.ts +14 -9
  99. package/src/cli/commands/contacts.ts +3 -0
  100. package/src/cli/commands/credentials.ts +170 -174
  101. package/src/cli/commands/doctor.ts +11 -8
  102. package/src/cli/commands/keys.ts +9 -9
  103. package/src/cli/commands/mcp.ts +46 -59
  104. package/src/cli/commands/memory.ts +16 -165
  105. package/src/cli/commands/oauth/apps.ts +68 -10
  106. package/src/cli/commands/oauth/connections.ts +475 -105
  107. package/src/cli/commands/oauth/index.ts +3 -3
  108. package/src/cli/commands/oauth/providers.ts +18 -4
  109. package/src/cli/commands/sessions.ts +5 -2
  110. package/src/cli/commands/skills.ts +173 -1
  111. package/src/cli/http-client.ts +0 -20
  112. package/src/cli/main-screen.tsx +2 -2
  113. package/src/cli/program.ts +5 -6
  114. package/src/cli.ts +20 -22
  115. package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
  116. package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
  117. package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +35 -11
  119. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  120. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  121. package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
  135. package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
  136. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
  137. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
  138. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
  139. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
  140. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
  141. package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
  142. package/src/config/bundled-skills/messaging/SKILL.md +1 -1
  143. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  144. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  145. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
  146. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
  148. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
  149. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
  150. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
  151. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
  152. package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
  153. package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
  154. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
  155. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
  156. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
  157. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
  158. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
  159. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
  160. package/src/config/bundled-tool-registry.ts +2 -5
  161. package/src/config/loader.ts +6 -42
  162. package/src/config/schema.ts +1 -12
  163. package/src/config/schemas/memory-lifecycle.ts +0 -9
  164. package/src/config/schemas/memory-processing.ts +0 -180
  165. package/src/config/schemas/memory-retrieval.ts +32 -104
  166. package/src/config/schemas/memory.ts +0 -10
  167. package/src/config/types.ts +0 -4
  168. package/src/contacts/contact-store.ts +39 -2
  169. package/src/contacts/contacts-write.ts +9 -0
  170. package/src/context/window-manager.ts +4 -1
  171. package/src/daemon/config-watcher.ts +55 -2
  172. package/src/daemon/daemon-control.ts +1 -1
  173. package/src/daemon/date-context.ts +114 -31
  174. package/src/daemon/handlers/config-ingress.ts +2 -2
  175. package/src/daemon/handlers/config-slack-channel.ts +59 -39
  176. package/src/daemon/handlers/config-telegram.ts +23 -14
  177. package/src/daemon/handlers/session-history.ts +1 -358
  178. package/src/daemon/handlers/sessions.ts +18 -13
  179. package/src/daemon/handlers/shared.ts +3 -17
  180. package/src/daemon/handlers/skills.ts +20 -1
  181. package/src/daemon/history-repair.ts +72 -8
  182. package/src/daemon/host-cu-proxy.ts +55 -26
  183. package/src/daemon/lifecycle.ts +39 -4
  184. package/src/daemon/mcp-reload-service.ts +2 -2
  185. package/src/daemon/message-types/computer-use.ts +1 -12
  186. package/src/daemon/message-types/memory.ts +4 -16
  187. package/src/daemon/message-types/messages.ts +1 -0
  188. package/src/daemon/message-types/sessions.ts +4 -42
  189. package/src/daemon/server.ts +6 -1
  190. package/src/daemon/session-agent-loop-handlers.ts +38 -0
  191. package/src/daemon/session-agent-loop.ts +334 -48
  192. package/src/daemon/session-error.ts +89 -6
  193. package/src/daemon/session-history.ts +17 -7
  194. package/src/daemon/session-media-retry.ts +6 -2
  195. package/src/daemon/session-memory.ts +69 -149
  196. package/src/daemon/session-process.ts +10 -1
  197. package/src/daemon/session-runtime-assembly.ts +49 -19
  198. package/src/daemon/session-slash.ts +3 -5
  199. package/src/daemon/session-surfaces.ts +4 -1
  200. package/src/daemon/session-tool-setup.ts +7 -1
  201. package/src/daemon/session.ts +12 -2
  202. package/src/email/providers/index.ts +2 -2
  203. package/src/instrument.ts +61 -1
  204. package/src/media/avatar-router.ts +1 -1
  205. package/src/memory/admin.ts +2 -191
  206. package/src/memory/canonical-guardian-store.ts +38 -2
  207. package/src/memory/conversation-crud.ts +0 -33
  208. package/src/memory/conversation-queries.ts +25 -83
  209. package/src/memory/db-init.ts +32 -0
  210. package/src/memory/embedding-backend.ts +84 -8
  211. package/src/memory/embedding-types.ts +9 -1
  212. package/src/memory/indexer.ts +7 -46
  213. package/src/memory/invite-store.ts +19 -0
  214. package/src/memory/items-extractor.ts +274 -76
  215. package/src/memory/job-handlers/backfill.ts +2 -127
  216. package/src/memory/job-handlers/cleanup.ts +2 -16
  217. package/src/memory/job-handlers/extraction.ts +2 -138
  218. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  219. package/src/memory/job-handlers/summarization.ts +3 -148
  220. package/src/memory/job-utils.ts +21 -59
  221. package/src/memory/jobs-store.ts +1 -159
  222. package/src/memory/jobs-worker.ts +9 -52
  223. package/src/memory/migrations/104-core-indexes.ts +3 -3
  224. package/src/memory/migrations/149-oauth-tables.ts +2 -0
  225. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  226. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  227. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  228. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  229. package/src/memory/migrations/154-drop-fts.ts +20 -0
  230. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  231. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  232. package/src/memory/migrations/157-invite-contact-id.ts +104 -0
  233. package/src/memory/migrations/index.ts +8 -0
  234. package/src/memory/migrations/registry.ts +6 -0
  235. package/src/memory/qdrant-client.ts +148 -51
  236. package/src/memory/raw-query.ts +1 -1
  237. package/src/memory/retriever.test.ts +294 -273
  238. package/src/memory/retriever.ts +421 -645
  239. package/src/memory/schema/calls.ts +2 -0
  240. package/src/memory/schema/contacts.ts +1 -0
  241. package/src/memory/schema/memory-core.ts +3 -48
  242. package/src/memory/schema/oauth.ts +2 -0
  243. package/src/memory/search/formatting.ts +263 -176
  244. package/src/memory/search/lexical.ts +1 -254
  245. package/src/memory/search/ranking.ts +0 -455
  246. package/src/memory/search/semantic.ts +100 -14
  247. package/src/memory/search/staleness.ts +47 -0
  248. package/src/memory/search/tier-classifier.ts +21 -0
  249. package/src/memory/search/types.ts +15 -77
  250. package/src/memory/task-memory-cleanup.ts +4 -6
  251. package/src/messaging/provider.ts +1 -1
  252. package/src/messaging/providers/gmail/adapter.ts +1 -1
  253. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  254. package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
  255. package/src/messaging/providers/whatsapp/adapter.ts +13 -9
  256. package/src/messaging/registry.ts +9 -5
  257. package/src/oauth/byo-connection.test.ts +40 -25
  258. package/src/oauth/connect-orchestrator.ts +4 -10
  259. package/src/oauth/connection-resolver.ts +20 -6
  260. package/src/oauth/manual-token-connection.ts +5 -5
  261. package/src/oauth/oauth-store.ts +183 -31
  262. package/src/oauth/platform-connection.test.ts +1 -1
  263. package/src/oauth/provider-behaviors.ts +503 -4
  264. package/src/oauth/seed-providers.ts +214 -8
  265. package/src/oauth/token-persistence.ts +31 -16
  266. package/src/permissions/defaults.ts +1 -0
  267. package/src/permissions/trust-store.ts +23 -1
  268. package/src/playbooks/playbook-compiler.ts +1 -1
  269. package/src/prompts/system-prompt.ts +18 -2
  270. package/src/providers/anthropic/client.ts +56 -126
  271. package/src/providers/types.ts +7 -1
  272. package/src/runtime/AGENTS.md +9 -0
  273. package/src/runtime/auth/route-policy.ts +6 -3
  274. package/src/runtime/channel-readiness-service.ts +48 -40
  275. package/src/runtime/guardian-reply-router.ts +24 -22
  276. package/src/runtime/http-server.ts +2 -2
  277. package/src/runtime/http-types.ts +2 -0
  278. package/src/runtime/invite-redemption-service.ts +72 -12
  279. package/src/runtime/invite-service.ts +43 -0
  280. package/src/runtime/middleware/twilio-validation.ts +1 -1
  281. package/src/runtime/pending-interactions.ts +2 -2
  282. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  283. package/src/runtime/routes/btw-routes.ts +10 -5
  284. package/src/runtime/routes/conversation-routes.ts +56 -11
  285. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  286. package/src/runtime/routes/integrations/slack/channel.ts +2 -2
  287. package/src/runtime/routes/integrations/telegram.ts +2 -2
  288. package/src/runtime/routes/integrations/twilio.ts +17 -17
  289. package/src/runtime/routes/invite-routes.ts +29 -4
  290. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  291. package/src/runtime/routes/memory-item-routes.ts +503 -0
  292. package/src/runtime/routes/secret-routes.ts +17 -0
  293. package/src/runtime/routes/session-management-routes.ts +3 -3
  294. package/src/runtime/routes/settings-routes.ts +3 -3
  295. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  296. package/src/runtime/routes/workspace-routes.ts +9 -4
  297. package/src/runtime/routes/workspace-utils.ts +8 -2
  298. package/src/schedule/integration-status.ts +26 -19
  299. package/src/security/keychain-broker-client.ts +17 -4
  300. package/src/security/oauth2.ts +6 -7
  301. package/src/security/secure-keys.ts +44 -19
  302. package/src/security/token-manager.ts +46 -39
  303. package/src/services/vercel-deploy.ts +0 -24
  304. package/src/signals/confirm.ts +78 -0
  305. package/src/signals/mcp-reload.ts +18 -0
  306. package/src/skills/catalog-install.ts +74 -18
  307. package/src/skills/skillssh-registry.ts +503 -0
  308. package/src/tools/assets/search.ts +5 -1
  309. package/src/tools/computer-use/definitions.ts +0 -10
  310. package/src/tools/computer-use/registry.ts +1 -1
  311. package/src/tools/credentials/vault.ts +22 -7
  312. package/src/tools/memory/definitions.ts +4 -13
  313. package/src/tools/memory/handlers.test.ts +83 -103
  314. package/src/tools/memory/handlers.ts +50 -85
  315. package/src/tools/network/script-proxy/session-manager.ts +8 -8
  316. package/src/tools/schedule/create.ts +10 -3
  317. package/src/tools/schedule/update.ts +8 -1
  318. package/src/tools/skills/load.ts +25 -2
  319. package/src/watcher/provider-types.ts +1 -1
  320. package/src/watcher/providers/github.ts +1 -1
  321. package/src/watcher/providers/gmail.ts +3 -3
  322. package/src/watcher/providers/google-calendar.ts +3 -3
  323. package/src/watcher/providers/linear.ts +1 -1
  324. package/src/__tests__/clarification-resolver.test.ts +0 -193
  325. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  326. package/src/__tests__/conflict-policy.test.ts +0 -269
  327. package/src/__tests__/conflict-store.test.ts +0 -372
  328. package/src/__tests__/contradiction-checker.test.ts +0 -361
  329. package/src/__tests__/entity-extractor.test.ts +0 -211
  330. package/src/__tests__/entity-search.test.ts +0 -1117
  331. package/src/__tests__/profile-compiler.test.ts +0 -392
  332. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  333. package/src/__tests__/session-profile-injection.test.ts +0 -557
  334. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  335. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  336. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  337. package/src/daemon/session-conflict-gate.ts +0 -167
  338. package/src/daemon/session-dynamic-profile.ts +0 -77
  339. package/src/memory/clarification-resolver.ts +0 -417
  340. package/src/memory/conflict-intent.ts +0 -205
  341. package/src/memory/conflict-policy.ts +0 -127
  342. package/src/memory/conflict-store.ts +0 -410
  343. package/src/memory/contradiction-checker.ts +0 -508
  344. package/src/memory/entity-extractor.ts +0 -535
  345. package/src/memory/format-recall.ts +0 -47
  346. package/src/memory/fts-reconciler.ts +0 -165
  347. package/src/memory/job-handlers/conflict.ts +0 -200
  348. package/src/memory/profile-compiler.ts +0 -195
  349. package/src/memory/recall-cache.ts +0 -117
  350. package/src/memory/search/entity.ts +0 -535
  351. package/src/memory/search/query-expansion.test.ts +0 -70
  352. package/src/memory/search/query-expansion.ts +0 -118
  353. package/src/runtime/routes/mcp-routes.ts +0 -20
@@ -3,6 +3,7 @@ import { orchestrateOAuthConnect } from "../../oauth/connect-orchestrator.js";
3
3
  import {
4
4
  disconnectOAuthProvider,
5
5
  getAppByProviderAndClientId,
6
+ getConnectionByProviderAndAccount,
6
7
  getMostRecentAppByProvider,
7
8
  getProvider,
8
9
  } from "../../oauth/oauth-store.js";
@@ -19,7 +20,7 @@ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../runtime/assistant-scope.js";
19
20
  import { credentialKey } from "../../security/credential-key.js";
20
21
  import {
21
22
  deleteSecureKeyAsync,
22
- getSecureKey,
23
+ getSecureKeyAsync,
23
24
  listSecureKeys,
24
25
  setSecureKeyAsync,
25
26
  } from "../../security/secure-keys.js";
@@ -72,6 +73,11 @@ class CredentialStoreTool implements Tool {
72
73
  type: "string",
73
74
  description: "Service name, e.g. gmail, github",
74
75
  },
76
+ account: {
77
+ type: "string",
78
+ description:
79
+ "Account identifier (e.g. email address) to target a specific connection when multiple accounts are connected for the same service. If omitted, uses the most recently connected account.",
80
+ },
75
81
  field: {
76
82
  type: "string",
77
83
  description: "Field name, e.g. password, username, recovery_email",
@@ -453,7 +459,19 @@ class CredentialStoreTool implements Tool {
453
459
  }
454
460
  // Also clean up any OAuth connection for this service (best-effort)
455
461
  try {
456
- const oauthResult = await disconnectOAuthProvider(service);
462
+ const accountHint = input.account as string | undefined;
463
+ let oauthResult: "disconnected" | "not-found" | "error";
464
+ if (accountHint) {
465
+ const targetConn = getConnectionByProviderAndAccount(
466
+ service,
467
+ accountHint,
468
+ );
469
+ oauthResult = targetConn
470
+ ? await disconnectOAuthProvider(service, undefined, targetConn.id)
471
+ : "not-found";
472
+ } else {
473
+ oauthResult = await disconnectOAuthProvider(service);
474
+ }
457
475
  if (oauthResult === "error") {
458
476
  log.warn(
459
477
  { service },
@@ -723,7 +741,7 @@ class CredentialStoreTool implements Tool {
723
741
  isError: true,
724
742
  };
725
743
 
726
- // Resolve aliases (e.g. "gmail" → "integration:gmail")
744
+ // Resolve aliases (e.g. "gmail" → "integration:google")
727
745
  const service = resolveService(rawService);
728
746
 
729
747
  // Code-side behavioral fields (identityVerifier, setup, etc.)
@@ -747,9 +765,7 @@ class CredentialStoreTool implements Tool {
747
765
  if (dbApp) {
748
766
  if (!clientId) clientId = dbApp.clientId;
749
767
  if (!clientSecret) {
750
- clientSecret = getSecureKey(
751
- `oauth_app/${dbApp.id}/client_secret`,
752
- );
768
+ clientSecret = await getSecureKeyAsync(dbApp.clientSecretCredentialPath);
753
769
  }
754
770
  }
755
771
  }
@@ -796,7 +812,6 @@ class CredentialStoreTool implements Tool {
796
812
  clientSecret,
797
813
  isInteractive: !!context.isInteractive,
798
814
  sendToClient: context.sendToClient,
799
- allowedTools: input.allowed_tools as string[] | undefined,
800
815
  ...(inputScopes ? { requestedScopes: inputScopes } : {}),
801
816
  onDeferredComplete: (deferredResult) => {
802
817
  // Emit oauth_connect_result to all connected SSE clients so the
@@ -3,7 +3,7 @@ import type { ToolDefinition } from "../../providers/types.js";
3
3
  export const memoryRecallDefinition: ToolDefinition = {
4
4
  name: "memory_recall",
5
5
  description:
6
- "Deep search across all memory sources (semantic, lexical, entity graph, recency) for specific information. Use this when you need to recall details about past conversations, decisions, preferences, project context, or any prior knowledge. Returns formatted memory context with item IDs for use with memory_manage.",
6
+ "Hybrid search across memory (semantic and recency) for specific information. Use this when you need to recall details about past conversations, decisions, preferences, project context, or any prior knowledge. Returns formatted memory context with item IDs for use with memory_manage.",
7
7
  input_schema: {
8
8
  type: "object",
9
9
  properties: {
@@ -11,10 +11,6 @@ export const memoryRecallDefinition: ToolDefinition = {
11
11
  type: "string",
12
12
  description: "The search query — be specific and descriptive",
13
13
  },
14
- max_results: {
15
- type: "number",
16
- description: "Maximum number of memory items to return (default: 10)",
17
- },
18
14
  scope: {
19
15
  type: "string",
20
16
  enum: ["default", "conversation"],
@@ -44,17 +40,12 @@ const memoryManageProperties = {
44
40
  kind: {
45
41
  type: "string" as const,
46
42
  enum: [
43
+ "identity",
47
44
  "preference",
48
- "fact",
45
+ "project",
49
46
  "decision",
50
- "profile",
51
- "relationship",
47
+ "constraint",
52
48
  "event",
53
- "opinion",
54
- "instruction",
55
- "style",
56
- "playbook",
57
- "learning",
58
49
  ],
59
50
  description: "Category of the memory item (required for save)",
60
51
  },
@@ -55,6 +55,7 @@ mock.module("../../memory/embedding-local.js", () => ({
55
55
  mock.module("../../memory/qdrant-client.js", () => ({
56
56
  getQdrantClient: () => ({
57
57
  searchWithFilter: async () => [],
58
+ hybridSearch: async () => [],
58
59
  upsertPoints: async () => {},
59
60
  deletePoints: async () => {},
60
61
  }),
@@ -93,14 +94,14 @@ import {
93
94
  memoryItemSources,
94
95
  messages,
95
96
  } from "../../memory/schema.js";
96
- import { handleMemoryRecall, type MemoryRecallToolResult } from "./handlers.js";
97
+ import type { MemoryRecallToolResult } from "./handlers.js";
98
+ import { handleMemoryRecall } from "./handlers.js";
97
99
 
98
100
  function clearTables() {
99
101
  const db = getDb();
100
102
  db.run("DELETE FROM memory_item_sources");
101
103
  db.run("DELETE FROM memory_items");
102
104
  db.run("DELETE FROM memory_segments");
103
- db.run("DELETE FROM memory_segment_fts");
104
105
  db.run("DELETE FROM messages");
105
106
  db.run("DELETE FROM conversations");
106
107
  }
@@ -237,7 +238,7 @@ function seedMemory() {
237
238
 
238
239
  insertItem(db, {
239
240
  id: "item-testing",
240
- kind: "fact",
241
+ kind: "identity",
241
242
  subject: "testing",
242
243
  statement: "The project uses bun test for unit testing",
243
244
  firstSeenAt: now - 20_000,
@@ -284,7 +285,7 @@ describe("handleMemoryRecall", () => {
284
285
 
285
286
  // ── Happy path ────────────────────────────────────────────────────
286
287
 
287
- test("returns formatted results from multiple sources", async () => {
288
+ test("returns valid result shape with Qdrant mocked empty", async () => {
288
289
  seedMemory();
289
290
 
290
291
  const result = await handleMemoryRecall(
@@ -292,35 +293,13 @@ describe("handleMemoryRecall", () => {
292
293
  TEST_CONFIG,
293
294
  );
294
295
 
296
+ // With Qdrant mocked empty, hybrid search returns nothing.
297
+ // Recency search also returns nothing (no conversationId passed to handler).
298
+ // The handler should return a valid result shape with zero results.
295
299
  expect(result.isError).toBe(false);
296
300
  const parsed = parseResult(result.content);
297
- expect(parsed.resultCount).toBeGreaterThan(0);
298
- expect(parsed.text.length).toBeGreaterThan(0);
299
- });
300
-
301
- test("respects max_results parameter", async () => {
302
- seedMemory();
303
-
304
- const result = await handleMemoryRecall(
305
- { query: "API design", max_results: 1 },
306
- TEST_CONFIG,
307
- );
308
-
309
- expect(result.isError).toBe(false);
310
- const parsed = parseResult(result.content);
311
- expect(parsed.resultCount).toBeLessThanOrEqual(1);
312
- });
313
-
314
- test("clamps max_results to 50", async () => {
315
- seedMemory();
316
-
317
- // Should not throw, max_results capped at 50
318
- const result = await handleMemoryRecall(
319
- { query: "API design", max_results: 100 },
320
- TEST_CONFIG,
321
- );
322
-
323
- expect(result.isError).toBe(false);
301
+ expect(typeof parsed.resultCount).toBe("number");
302
+ expect(typeof parsed.text).toBe("string");
324
303
  });
325
304
 
326
305
  // ── Empty results ─────────────────────────────────────────────────
@@ -336,10 +315,8 @@ describe("handleMemoryRecall", () => {
336
315
  const parsed = parseResult(result.content);
337
316
  expect(parsed.resultCount).toBe(0);
338
317
  expect(parsed.text).toBe("No matching memories found.");
339
- expect(parsed.sources.lexical).toBe(0);
340
318
  expect(parsed.sources.semantic).toBe(0);
341
319
  expect(parsed.sources.recency).toBe(0);
342
- expect(parsed.sources.entity).toBe(0);
343
320
  });
344
321
 
345
322
  // ── Degraded mode ─────────────────────────────────────────────────
@@ -399,11 +376,13 @@ describe("handleMemoryRecall", () => {
399
376
  // Not degraded because embeddings are optional
400
377
  expect(parsed.degraded).toBe(false);
401
378
  expect(parsed.sources.semantic).toBe(0);
402
- // Still returns results from non-semantic sources (direct item search)
403
- expect(parsed.resultCount).toBeGreaterThan(0);
379
+ // With FTS/direct-item search removed, only Qdrant hybrid search and
380
+ // recency search remain. Both return empty here (Qdrant mocked,
381
+ // no conversationId passed). The handler returns a valid empty result.
382
+ expect(parsed.resultCount).toBe(0);
404
383
  });
405
384
 
406
- test("returns lexical results in degraded mode", async () => {
385
+ test("gracefully returns empty in degraded mode without embeddings", async () => {
407
386
  seedMemory();
408
387
 
409
388
  const degradedConfig: AssistantConfig = {
@@ -425,70 +404,76 @@ describe("handleMemoryRecall", () => {
425
404
 
426
405
  expect(result.isError).toBe(false);
427
406
  const parsed = parseResult(result.content);
428
- // Direct item search should still find items even without embeddings
429
- expect(parsed.resultCount).toBeGreaterThan(0);
407
+ // With FTS removed and Qdrant mocked, no retrieval path finds items.
408
+ // The handler returns a valid empty result without throwing.
409
+ expect(typeof parsed.resultCount).toBe("number");
430
410
  });
431
411
 
432
412
  // ── Scope filtering ───────────────────────────────────────────────
433
413
 
434
- test("scope 'conversation' restricts to current thread", async () => {
414
+ test("scope 'conversation' passes scope policy override to retriever", async () => {
415
+ // Seed a conversation with segments in the target scope and in a different
416
+ // scope. With scope="conversation", only the target scope's segments should
417
+ // be returned (fallbackToDefault=false).
435
418
  const db = getDb();
436
419
  const now = Date.now();
420
+ const convId = "conv-scope-a";
421
+
422
+ insertConversation(db, convId, now - 10_000);
423
+ insertMessage(
424
+ db,
425
+ "msg-scope-a",
426
+ convId,
427
+ "user",
428
+ "scoped data for conversation A",
429
+ now - 5_000,
430
+ );
437
431
 
438
- // Insert item in "conv-scope-a" scope
439
- insertItem(db, {
440
- id: "item-scope-a",
441
- kind: "fact",
442
- subject: "scoped data",
443
- statement: "This item is scoped to conversation A",
444
- firstSeenAt: now - 10_000,
445
- scopeId: "conv-scope-a",
446
- });
432
+ // Insert a segment scoped to this conversation's scope
433
+ db.run(`
434
+ INSERT INTO memory_segments (id, message_id, conversation_id, role, segment_index, text, token_estimate, scope_id, created_at, updated_at)
435
+ VALUES ('seg-scope-a', 'msg-scope-a', '${convId}', 'user', 0, 'Conversation-scoped data for conversation A', 8, '${convId}', ${
436
+ now - 5_000
437
+ }, ${now - 5_000})
438
+ `);
439
+
440
+ // Insert an out-of-scope segment that should NOT be returned
441
+ db.run(`
442
+ INSERT INTO memory_segments (id, message_id, conversation_id, role, segment_index, text, token_estimate, scope_id, created_at, updated_at)
443
+ VALUES ('seg-scope-other', 'msg-scope-a', '${convId}', 'user', 1, 'Out-of-scope data from a different scope', 8, 'other-scope', ${
444
+ now - 5_000
445
+ }, ${now - 5_000})
446
+ `);
447
447
 
448
- // Insert item in default scope
449
- insertItem(db, {
450
- id: "item-default",
451
- kind: "fact",
452
- subject: "default data",
453
- statement: "This item is in the default scope about scoped data",
454
- firstSeenAt: now - 10_000,
455
- scopeId: "default",
456
- });
457
-
458
- // Query with scope="conversation" and scopeId="conv-scope-a"
459
- // should restrict to only that scope (no fallback to default)
460
448
  const result = await handleMemoryRecall(
461
- { query: "scoped data", scope: "conversation" },
449
+ { query: "data", scope: "conversation" },
462
450
  TEST_CONFIG,
463
- "conv-scope-a",
451
+ convId,
452
+ convId,
464
453
  );
465
454
 
466
455
  expect(result.isError).toBe(false);
467
456
  const parsed = parseResult(result.content);
468
-
469
- // When scope is "conversation", fallbackToDefault is false,
470
- // so only items from conv-scope-a should appear
471
- expect(parsed.resultCount).toBeGreaterThan(0);
472
- expect(parsed.text).toContain("scoped to conversation A");
473
- expect(parsed.text).not.toContain("default scope");
457
+ // With conversation scope, only the conversation-scoped segment is returned.
458
+ // The other-scope segment should be excluded (fallbackToDefault=false).
459
+ expect(parsed.sources.recency).toBe(1);
460
+ expect(parsed.text).toContain("Conversation-scoped data");
461
+ expect(parsed.text).not.toContain("Out-of-scope data");
474
462
  });
475
463
 
476
- test("default scope includes fallback to default scope", async () => {
464
+ test("default scope handler invocation does not error", async () => {
477
465
  const db = getDb();
478
466
  const now = Date.now();
479
467
 
480
- // Insert item in default scope
481
468
  insertItem(db, {
482
469
  id: "item-fallback",
483
- kind: "fact",
470
+ kind: "identity",
484
471
  subject: "global knowledge",
485
472
  statement: "This global knowledge should be accessible from any scope",
486
473
  firstSeenAt: now - 10_000,
487
474
  scopeId: "default",
488
475
  });
489
476
 
490
- // Query with scope="default" (the default) and a specific scopeId
491
- // should include fallback to default scope
492
477
  const result = await handleMemoryRecall(
493
478
  { query: "global knowledge" },
494
479
  TEST_CONFIG,
@@ -497,37 +482,36 @@ describe("handleMemoryRecall", () => {
497
482
 
498
483
  expect(result.isError).toBe(false);
499
484
  const parsed = parseResult(result.content);
500
- // Default scope items should be accessible
501
- expect(parsed.resultCount).toBeGreaterThan(0);
502
- expect(parsed.text).toContain("global knowledge");
485
+ // With Qdrant mocked and no conversation segments, the retriever returns
486
+ // empty. Handler should still return a valid result shape.
487
+ expect(typeof parsed.resultCount).toBe("number");
503
488
  });
504
489
 
505
490
  // ── Error handling ────────────────────────────────────────────────
506
491
 
507
492
  test("retrieval failure returns error message, does not throw", async () => {
508
- // Create a config that will cause the retrieval pipeline to throw
509
- // by making memory disabled in a way that collectAndMergeCandidates breaks.
510
- // We mock the retriever to throw an error.
511
- const badConfig: AssistantConfig = {
512
- ...TEST_CONFIG,
513
- memory: {
514
- ...TEST_CONFIG.memory,
515
- // Force retrieval with impossible settings to trigger an error path
516
- retrieval: {
517
- ...TEST_CONFIG.memory.retrieval,
518
- lexicalTopK: -1, // may cause issues in search
519
- },
520
- },
521
- };
522
-
523
- // Even if the query fails internally, the handler should catch and return
524
- // an error result rather than throwing
525
- const result = await handleMemoryRecall({ query: "test query" }, badConfig);
526
-
527
- // The function should either succeed gracefully or return an error
528
- // but never throw
529
- expect(typeof result.content).toBe("string");
530
- expect(typeof result.isError).toBe("boolean");
493
+ // Mock buildMemoryRecall to throw, simulating an internal retrieval failure
494
+ const retrieverModule = await import("../../memory/retriever.js");
495
+ const original = retrieverModule.buildMemoryRecall;
496
+ (retrieverModule as Record<string, unknown>).buildMemoryRecall =
497
+ async () => {
498
+ throw new Error("Simulated retrieval failure");
499
+ };
500
+
501
+ try {
502
+ const result = await handleMemoryRecall(
503
+ { query: "test query" },
504
+ TEST_CONFIG,
505
+ );
506
+
507
+ // The handler should catch the error and return an error result,
508
+ // never throw
509
+ expect(result.isError).toBe(true);
510
+ expect(result.content).toContain("Simulated retrieval failure");
511
+ } finally {
512
+ // Restore original implementation
513
+ (retrieverModule as Record<string, unknown>).buildMemoryRecall = original;
514
+ }
531
515
  });
532
516
 
533
517
  test("result shape matches MemoryRecallToolResult when successful", async () => {
@@ -546,10 +530,8 @@ describe("handleMemoryRecall", () => {
546
530
  expect(typeof parsed.resultCount).toBe("number");
547
531
  expect(typeof parsed.degraded).toBe("boolean");
548
532
  expect(typeof parsed.sources).toBe("object");
549
- expect(typeof parsed.sources.lexical).toBe("number");
550
533
  expect(typeof parsed.sources.semantic).toBe("number");
551
534
  expect(typeof parsed.sources.recency).toBe("number");
552
- expect(typeof parsed.sources.entity).toBe("number");
553
535
  });
554
536
 
555
537
  test("empty result shape matches MemoryRecallToolResult", async () => {
@@ -564,9 +546,7 @@ describe("handleMemoryRecall", () => {
564
546
  expect(parsed.text).toBe("No matching memories found.");
565
547
  expect(parsed.resultCount).toBe(0);
566
548
  expect(typeof parsed.degraded).toBe("boolean");
567
- expect(parsed.sources.lexical).toBe(0);
568
549
  expect(parsed.sources.semantic).toBe(0);
569
550
  expect(parsed.sources.recency).toBe(0);
570
- expect(parsed.sources.entity).toBe(0);
571
551
  });
572
552
  });
@@ -3,17 +3,9 @@ import { v4 as uuid } from "uuid";
3
3
 
4
4
  import type { AssistantConfig } from "../../config/types.js";
5
5
  import { getDb } from "../../memory/db.js";
6
- import {
7
- getMemoryBackendStatus,
8
- logMemoryEmbeddingWarning,
9
- } from "../../memory/embedding-backend.js";
10
6
  import { computeMemoryFingerprint } from "../../memory/fingerprint.js";
11
- import { formatRecallText } from "../../memory/format-recall.js";
12
7
  import { enqueueMemoryJob } from "../../memory/jobs-store.js";
13
- import {
14
- collectAndMergeCandidates,
15
- embedWithRetry,
16
- } from "../../memory/retriever.js";
8
+ import { buildMemoryRecall } from "../../memory/retriever.js";
17
9
  import { memoryItems } from "../../memory/schema.js";
18
10
  import type { ScopePolicyOverride } from "../../memory/search/types.js";
19
11
  import { getLogger } from "../../util/logger.js";
@@ -39,21 +31,37 @@ export async function handleMemorySave(
39
31
  };
40
32
  }
41
33
 
42
- const kind = args.kind;
34
+ const rawKind = args.kind;
43
35
  const validKinds = new Set([
36
+ "identity",
44
37
  "preference",
45
- "fact",
38
+ "project",
46
39
  "decision",
47
- "profile",
48
- "relationship",
40
+ "constraint",
49
41
  "event",
50
- "opinion",
51
- "instruction",
52
- "style",
53
- "playbook",
54
- "learning",
55
42
  ]);
56
- if (typeof kind !== "string" || !validKinds.has(kind)) {
43
+ /** Maps old kind names to their new equivalents for backwards compat. */
44
+ const kindMigrationMap: Record<string, string> = {
45
+ profile: "identity",
46
+ fact: "identity",
47
+ relationship: "identity",
48
+ opinion: "preference",
49
+ todo: "project",
50
+ instruction: "constraint",
51
+ style: "preference",
52
+ playbook: "constraint",
53
+ learning: "identity",
54
+ };
55
+ if (typeof rawKind !== "string") {
56
+ return {
57
+ content: `Error: kind is required and must be one of: ${[
58
+ ...validKinds,
59
+ ].join(", ")}`,
60
+ isError: true,
61
+ };
62
+ }
63
+ const kind = kindMigrationMap[rawKind] ?? rawKind;
64
+ if (!validKinds.has(kind)) {
57
65
  return {
58
66
  content: `Error: kind is required and must be one of: ${[
59
67
  ...validKinds,
@@ -255,10 +263,8 @@ export interface MemoryRecallToolResult {
255
263
  degraded: boolean;
256
264
  items: Array<{ id: string; type: string; kind: string }>;
257
265
  sources: {
258
- lexical: number;
259
266
  semantic: number;
260
267
  recency: number;
261
- entity: number;
262
268
  };
263
269
  }
264
270
 
@@ -276,11 +282,6 @@ export async function handleMemoryRecall(
276
282
  };
277
283
  }
278
284
 
279
- const maxResults =
280
- typeof args.max_results === "number" && args.max_results > 0
281
- ? Math.min(args.max_results, 50)
282
- : 10;
283
-
284
285
  const scope =
285
286
  typeof args.scope === "string" && args.scope.trim().length > 0
286
287
  ? args.scope.trim()
@@ -298,54 +299,28 @@ export async function handleMemoryRecall(
298
299
  try {
299
300
  const trimmedQuery = query.trim();
300
301
 
301
- // Generate embedding vector (graceful degradation if unavailable)
302
- let queryVector: number[] | null = null;
303
- let provider: string | undefined;
304
- let model: string | undefined;
305
- let degraded = false;
306
-
307
- const backendStatus = getMemoryBackendStatus(config);
308
- if (backendStatus.provider) {
309
- try {
310
- const embedded = await embedWithRetry(config, [trimmedQuery]);
311
- queryVector = embedded.vectors[0] ?? null;
312
- provider = embedded.provider;
313
- model = embedded.model;
314
- } catch (err) {
315
- logMemoryEmbeddingWarning(err, "query");
316
- degraded = !!config.memory.embeddings.required;
317
- }
318
- } else {
319
- degraded = backendStatus.degraded;
320
- }
321
-
322
- // Run the full retrieval pipeline with all sources enabled
323
- const collected = await collectAndMergeCandidates(trimmedQuery, config, {
324
- queryVector,
325
- provider,
326
- model,
327
- conversationId,
328
- scopeId,
329
- scopePolicyOverride,
330
- });
331
-
332
- if (collected.semanticSearchFailed || collected.semanticUnavailable) {
333
- degraded = true;
334
- }
302
+ // Use the unified recall pipeline
303
+ const recall = await buildMemoryRecall(
304
+ trimmedQuery,
305
+ conversationId ?? "",
306
+ config,
307
+ {
308
+ scopeId,
309
+ scopePolicyOverride,
310
+ },
311
+ );
335
312
 
336
- const candidates = collected.merged.slice(0, maxResults);
313
+ const degraded = recall.degraded;
337
314
 
338
- if (candidates.length === 0) {
315
+ if (recall.selectedCount === 0 || recall.injectedText.length === 0) {
339
316
  const result: MemoryRecallToolResult = {
340
317
  text: "No matching memories found.",
341
318
  resultCount: 0,
342
319
  degraded,
343
320
  items: [],
344
321
  sources: {
345
- lexical: 0,
346
- semantic: 0,
347
- recency: 0,
348
- entity: 0,
322
+ semantic: recall.semanticHits,
323
+ recency: recall.recencyHits,
349
324
  },
350
325
  };
351
326
  return {
@@ -354,28 +329,18 @@ export async function handleMemoryRecall(
354
329
  };
355
330
  }
356
331
 
357
- // Format candidates into readable text using the shared formatter
358
- const formatted = formatRecallText(candidates, {
359
- format: config.memory.retrieval.injectionFormat,
360
- maxTokens: config.memory.retrieval.maxInjectTokens,
361
- });
362
-
363
- const items = formatted.selected.map((c) => ({
364
- id: c.id,
365
- type: c.type,
366
- kind: c.kind,
367
- }));
368
-
369
332
  const result: MemoryRecallToolResult = {
370
- text: formatted.text,
371
- resultCount: formatted.selected.length,
333
+ text: recall.injectedText,
334
+ resultCount: recall.selectedCount,
372
335
  degraded,
373
- items,
336
+ items: recall.topCandidates.map((c) => ({
337
+ id: c.key,
338
+ type: c.type,
339
+ kind: c.kind,
340
+ })),
374
341
  sources: {
375
- lexical: collected.lexical.length,
376
- semantic: collected.semantic.length,
377
- recency: collected.recency.length,
378
- entity: collected.entity.length,
342
+ semantic: recall.semanticHits,
343
+ recency: recall.recencyHits,
379
344
  },
380
345
  };
381
346