@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
@@ -1,535 +0,0 @@
1
- import { and, desc, eq, inArray, isNull, or } from "drizzle-orm";
2
-
3
- import type { MemoryEntityConfig } from "../../config/types.js";
4
- import { getLogger } from "../../util/logger.js";
5
- import { getDb, rawAll } from "../db.js";
6
- import {
7
- memoryEntityRelations,
8
- memoryItemEntities,
9
- memoryItems,
10
- memoryItemSources,
11
- } from "../schema.js";
12
- import { computeRecencyScore } from "./ranking.js";
13
- import type {
14
- Candidate,
15
- CandidateSource,
16
- CandidateType,
17
- EntitySearchResult,
18
- MatchedEntityRow,
19
- TraversalOptions,
20
- TraversalResult,
21
- TraversalStep,
22
- } from "./types.js";
23
-
24
- const log = getLogger("memory-retriever");
25
-
26
- /**
27
- * Entity-based retrieval: match seed entities from query text, fetch directly
28
- * linked items, and optionally expand one hop across entity relations.
29
- */
30
- export function entitySearch(
31
- query: string,
32
- entityConfig: MemoryEntityConfig,
33
- scopeIds?: string[],
34
- excludedMessageIds: string[] = [],
35
- ): EntitySearchResult {
36
- const trimmed = query.trim();
37
- if (trimmed.length === 0) return emptyEntitySearchResult();
38
-
39
- const relationConfig = entityConfig.relationRetrieval;
40
- const matchedEntities = findMatchedEntities(
41
- trimmed,
42
- relationConfig.enabled ? relationConfig.maxSeedEntities : 20,
43
- );
44
- if (matchedEntities.length === 0) return emptyEntitySearchResult();
45
-
46
- const seedEntityIds = matchedEntities.map((row) => row.id);
47
- const directCandidates = getEntityLinkedItemCandidates(seedEntityIds, {
48
- scopeIds,
49
- excludedMessageIds,
50
- source: "entity_direct",
51
- });
52
-
53
- if (!relationConfig.enabled) {
54
- return {
55
- candidates: directCandidates,
56
- relationSeedEntityCount: 0,
57
- relationTraversedEdgeCount: 0,
58
- relationNeighborEntityCount: 0,
59
- relationExpandedItemCount: 0,
60
- };
61
- }
62
-
63
- const relationSeedEntityCount = seedEntityIds.length;
64
-
65
- const {
66
- neighborEntityIds,
67
- traversedEdgeCount: relationTraversedEdgeCount,
68
- neighborDepths,
69
- } = findNeighborEntities(seedEntityIds, {
70
- maxEdges: relationConfig.maxEdges,
71
- maxNeighborEntities: relationConfig.maxNeighborEntities,
72
- maxDepth: relationConfig.maxDepth,
73
- });
74
- const relationNeighborEntityCount = neighborEntityIds.length;
75
- const directItemIds = new Set(
76
- directCandidates.map((candidate) => candidate.id),
77
- );
78
- const relationCandidates = getEntityLinkedItemCandidates(neighborEntityIds, {
79
- scopeIds,
80
- excludedMessageIds,
81
- source: "entity_relation",
82
- excludeItemIds: directItemIds,
83
- });
84
- const relationExpandedItemCount = relationCandidates.length;
85
-
86
- // Build candidate key → BFS depth map so ranking can apply distance-based decay
87
- const candidateDepths = new Map<string, number>();
88
- if (relationCandidates.length > 0 && neighborDepths.size > 0) {
89
- const db = getDb();
90
- const itemIds = relationCandidates.map((c) => c.id);
91
- const links = db
92
- .select({
93
- memoryItemId: memoryItemEntities.memoryItemId,
94
- entityId: memoryItemEntities.entityId,
95
- })
96
- .from(memoryItemEntities)
97
- .where(inArray(memoryItemEntities.memoryItemId, itemIds))
98
- .all();
99
-
100
- // For each item, find the minimum depth among its linked neighbor entities
101
- const itemDepthMap = new Map<string, number>();
102
- for (const link of links) {
103
- const depth = neighborDepths.get(link.entityId);
104
- if (depth !== undefined) {
105
- const existing = itemDepthMap.get(link.memoryItemId);
106
- if (existing === undefined || depth < existing) {
107
- itemDepthMap.set(link.memoryItemId, depth);
108
- }
109
- }
110
- }
111
-
112
- for (const candidate of relationCandidates) {
113
- const depth = itemDepthMap.get(candidate.id);
114
- if (depth !== undefined) {
115
- candidateDepths.set(candidate.key, depth);
116
- }
117
- }
118
- }
119
-
120
- return {
121
- candidates: [...directCandidates, ...relationCandidates],
122
- relationSeedEntityCount,
123
- relationTraversedEdgeCount,
124
- relationNeighborEntityCount,
125
- relationExpandedItemCount,
126
- candidateDepths,
127
- };
128
- }
129
-
130
- export function emptyEntitySearchResult(): EntitySearchResult {
131
- return {
132
- candidates: [],
133
- relationSeedEntityCount: 0,
134
- relationTraversedEdgeCount: 0,
135
- relationNeighborEntityCount: 0,
136
- relationExpandedItemCount: 0,
137
- candidateDepths: new Map(),
138
- };
139
- }
140
-
141
- export function findMatchedEntities(
142
- query: string,
143
- maxMatches: number,
144
- ): MatchedEntityRow[] {
145
- const trimmed = query.trim();
146
- if (trimmed.length === 0) return [];
147
-
148
- const safeLimit = Math.max(1, Math.floor(maxMatches));
149
-
150
- // Tokenize query into words for entity matching (min length 3 to reduce false positives)
151
- const tokens = trimmed
152
- .toLowerCase()
153
- .split(/[^a-z0-9_.-]+/g)
154
- .filter((t) => t.length >= 3);
155
- const fullQuery = trimmed.toLowerCase();
156
-
157
- // Use exact matching on entity names and json_each() for individual alias values.
158
- // Also match the full trimmed query to support multi-word entity names (e.g. "Visual Studio Code").
159
- // When tokens is empty (all words < 3 chars), only match on fullQuery.
160
- let entityQuery: string;
161
- let queryParams: string[];
162
- if (tokens.length > 0) {
163
- const namePlaceholders = tokens.map(() => "?").join(",");
164
- entityQuery = `
165
- SELECT DISTINCT me.id, me.name, me.type, me.aliases, me.mention_count
166
- FROM memory_entities me
167
- WHERE LOWER(me.name) IN (${namePlaceholders}) OR LOWER(me.name) = ?
168
- UNION
169
- SELECT DISTINCT me.id, me.name, me.type, me.aliases, me.mention_count
170
- FROM memory_entities me, json_each(me.aliases) je
171
- WHERE me.aliases IS NOT NULL AND (LOWER(je.value) IN (${namePlaceholders}) OR LOWER(je.value) = ?)
172
- LIMIT ${safeLimit}
173
- `;
174
- queryParams = [...tokens, fullQuery, ...tokens, fullQuery];
175
- } else {
176
- entityQuery = `
177
- SELECT DISTINCT me.id, me.name, me.type, me.aliases, me.mention_count
178
- FROM memory_entities me
179
- WHERE LOWER(me.name) = ?
180
- UNION
181
- SELECT DISTINCT me.id, me.name, me.type, me.aliases, me.mention_count
182
- FROM memory_entities me, json_each(me.aliases) je
183
- WHERE me.aliases IS NOT NULL AND LOWER(je.value) = ?
184
- LIMIT ${safeLimit}
185
- `;
186
- queryParams = [fullQuery, fullQuery];
187
- }
188
-
189
- try {
190
- return rawAll<MatchedEntityRow>(entityQuery, ...queryParams);
191
- } catch (err) {
192
- log.warn({ err }, "Entity search query failed");
193
- return [];
194
- }
195
- }
196
-
197
- /**
198
- * BFS traversal across entity relations with visited-set cycle detection
199
- * and configurable max depth to prevent unbounded graph walking.
200
- */
201
- export function findNeighborEntities(
202
- seedEntityIds: string[],
203
- opts: TraversalOptions,
204
- ): TraversalResult {
205
- const {
206
- maxEdges,
207
- maxNeighborEntities,
208
- maxDepth = 3,
209
- relationTypes,
210
- entityTypes,
211
- directed,
212
- } = opts;
213
- if (
214
- seedEntityIds.length === 0 ||
215
- maxEdges <= 0 ||
216
- maxNeighborEntities <= 0 ||
217
- maxDepth <= 0
218
- ) {
219
- return {
220
- neighborEntityIds: [],
221
- traversedEdgeCount: 0,
222
- neighborDepths: new Map(),
223
- };
224
- }
225
-
226
- const db = getDb();
227
- const visited = new Set<string>(seedEntityIds);
228
- const neighbors: string[] = [];
229
- const neighborDepths = new Map<string, number>();
230
- let totalEdgesTraversed = 0;
231
- const filterByEntityType = entityTypes && entityTypes.length > 0;
232
-
233
- // BFS frontier starts with seed entities
234
- let frontier = [...seedEntityIds];
235
-
236
- for (let depth = 0; depth < maxDepth; depth++) {
237
- if (frontier.length === 0 || neighbors.length >= maxNeighborEntities) break;
238
-
239
- const edgeBudget = maxEdges - totalEdgesTraversed;
240
- if (edgeBudget <= 0) break;
241
-
242
- let rows: Array<{ sourceEntityId: string; targetEntityId: string }>;
243
-
244
- if (filterByEntityType) {
245
- // When filtering by entity type, JOIN with memoryEntities on the neighbor
246
- // side so non-matching edges are excluded at the SQL level and don't
247
- // consume the edge budget.
248
- const relationTypeCondition =
249
- relationTypes && relationTypes.length > 0
250
- ? `AND r.relation IN (${relationTypes.map(() => "?").join(",")})`
251
- : "";
252
- const entityTypeFilter = `AND me.type IN (${entityTypes
253
- .map(() => "?")
254
- .join(",")})`;
255
- const frontierPlaceholders = frontier.map(() => "?").join(",");
256
- const limit = Math.max(1, edgeBudget);
257
-
258
- const relationParams =
259
- relationTypes && relationTypes.length > 0 ? relationTypes : [];
260
-
261
- type EdgeRow = { sourceEntityId: string; targetEntityId: string };
262
-
263
- if (directed) {
264
- // GROUP BY deduplicates entity pairs that have multiple relation rows
265
- const q1 = `
266
- SELECT r.source_entity_id AS sourceEntityId, r.target_entity_id AS targetEntityId
267
- FROM memory_entity_relations r
268
- INNER JOIN memory_entities me ON me.id = r.target_entity_id
269
- WHERE r.source_entity_id IN (${frontierPlaceholders})
270
- ${relationTypeCondition} ${entityTypeFilter}
271
- GROUP BY r.source_entity_id, r.target_entity_id
272
- ORDER BY MAX(r.last_seen_at) DESC
273
- LIMIT ?
274
- `;
275
- rows = rawAll<EdgeRow>(
276
- q1,
277
- ...frontier,
278
- ...relationParams,
279
- ...entityTypes,
280
- limit,
281
- );
282
- } else {
283
- // Combine both directions in a single query with global recency
284
- // ordering so the edge budget isn't direction-biased.
285
- const q = `
286
- SELECT sourceEntityId, targetEntityId FROM (
287
- SELECT r.source_entity_id AS sourceEntityId, r.target_entity_id AS targetEntityId, r.last_seen_at
288
- FROM memory_entity_relations r
289
- INNER JOIN memory_entities me ON me.id = r.target_entity_id
290
- WHERE r.source_entity_id IN (${frontierPlaceholders})
291
- ${relationTypeCondition} ${entityTypeFilter}
292
- UNION ALL
293
- SELECT r.source_entity_id AS sourceEntityId, r.target_entity_id AS targetEntityId, r.last_seen_at
294
- FROM memory_entity_relations r
295
- INNER JOIN memory_entities me ON me.id = r.source_entity_id
296
- WHERE r.target_entity_id IN (${frontierPlaceholders})
297
- ${relationTypeCondition} ${entityTypeFilter}
298
- )
299
- GROUP BY sourceEntityId, targetEntityId
300
- ORDER BY MAX(last_seen_at) DESC
301
- LIMIT ?
302
- `;
303
- rows = rawAll<EdgeRow>(
304
- q,
305
- ...frontier,
306
- ...relationParams,
307
- ...entityTypes,
308
- ...frontier,
309
- ...relationParams,
310
- ...entityTypes,
311
- limit,
312
- );
313
- }
314
- } else {
315
- const frontierCondition = directed
316
- ? inArray(memoryEntityRelations.sourceEntityId, frontier)
317
- : or(
318
- inArray(memoryEntityRelations.sourceEntityId, frontier),
319
- inArray(memoryEntityRelations.targetEntityId, frontier),
320
- );
321
- const whereCondition =
322
- relationTypes && relationTypes.length > 0
323
- ? and(
324
- frontierCondition,
325
- inArray(memoryEntityRelations.relation, relationTypes),
326
- )
327
- : frontierCondition;
328
-
329
- rows = db
330
- .select({
331
- sourceEntityId: memoryEntityRelations.sourceEntityId,
332
- targetEntityId: memoryEntityRelations.targetEntityId,
333
- })
334
- .from(memoryEntityRelations)
335
- .where(whereCondition)
336
- .orderBy(desc(memoryEntityRelations.lastSeenAt))
337
- .limit(Math.max(1, edgeBudget))
338
- .all();
339
- }
340
-
341
- totalEdgesTraversed += rows.length;
342
-
343
- const nextFrontier: string[] = [];
344
- const frontierSet = new Set(frontier);
345
- for (const row of rows) {
346
- if (neighbors.length >= maxNeighborEntities) break;
347
- // In directed mode, only follow source→target (frontier is always on source side)
348
- if (
349
- frontierSet.has(row.sourceEntityId) &&
350
- !visited.has(row.targetEntityId)
351
- ) {
352
- visited.add(row.targetEntityId);
353
- neighbors.push(row.targetEntityId);
354
- nextFrontier.push(row.targetEntityId);
355
- neighborDepths.set(row.targetEntityId, depth + 1);
356
- }
357
- if (directed) continue;
358
- if (neighbors.length >= maxNeighborEntities) break;
359
- if (
360
- frontierSet.has(row.targetEntityId) &&
361
- !visited.has(row.sourceEntityId)
362
- ) {
363
- visited.add(row.sourceEntityId);
364
- neighbors.push(row.sourceEntityId);
365
- nextFrontier.push(row.sourceEntityId);
366
- neighborDepths.set(row.sourceEntityId, depth + 1);
367
- }
368
- }
369
-
370
- frontier = nextFrontier;
371
- }
372
-
373
- return {
374
- neighborEntityIds: neighbors.slice(0, maxNeighborEntities),
375
- traversedEdgeCount: totalEdgesTraversed,
376
- neighborDepths,
377
- };
378
- }
379
-
380
- export function getEntityLinkedItemCandidates(
381
- entityIds: string[],
382
- opts: {
383
- scopeIds?: string[];
384
- excludedMessageIds?: string[];
385
- source: CandidateSource;
386
- excludeItemIds?: Set<string>;
387
- },
388
- ): Candidate[] {
389
- if (entityIds.length === 0) return [];
390
- const excludedMessageIds = opts.excludedMessageIds ?? [];
391
-
392
- const db = getDb();
393
- const linkedRows = db
394
- .select({
395
- memoryItemId: memoryItemEntities.memoryItemId,
396
- })
397
- .from(memoryItemEntities)
398
- .where(inArray(memoryItemEntities.entityId, entityIds))
399
- .all();
400
-
401
- if (linkedRows.length === 0) return [];
402
-
403
- const itemIds = [
404
- ...new Set(linkedRows.map((row) => row.memoryItemId)),
405
- ].filter((itemId) => !opts.excludeItemIds?.has(itemId));
406
- if (itemIds.length === 0) return [];
407
-
408
- const itemConditions = [
409
- inArray(memoryItems.id, itemIds),
410
- eq(memoryItems.status, "active"),
411
- isNull(memoryItems.invalidAt),
412
- ];
413
- if (opts.scopeIds && opts.scopeIds.length > 0) {
414
- itemConditions.push(inArray(memoryItems.scopeId, opts.scopeIds));
415
- }
416
- let items = db
417
- .select()
418
- .from(memoryItems)
419
- .where(and(...itemConditions))
420
- .all();
421
- if (items.length === 0) return [];
422
-
423
- if (excludedMessageIds.length > 0) {
424
- const excludedSet = new Set(excludedMessageIds);
425
- const sources = db
426
- .select({
427
- memoryItemId: memoryItemSources.memoryItemId,
428
- messageId: memoryItemSources.messageId,
429
- })
430
- .from(memoryItemSources)
431
- .where(
432
- inArray(
433
- memoryItemSources.memoryItemId,
434
- items.map((item) => item.id),
435
- ),
436
- )
437
- .all();
438
- const hasAnySource = new Set<string>();
439
- const hasNonExcludedSource = new Set<string>();
440
- for (const source of sources) {
441
- hasAnySource.add(source.memoryItemId);
442
- if (!excludedSet.has(source.messageId)) {
443
- hasNonExcludedSource.add(source.memoryItemId);
444
- }
445
- }
446
- items = items.filter(
447
- (item) => !hasAnySource.has(item.id) || hasNonExcludedSource.has(item.id),
448
- );
449
- }
450
- if (items.length === 0) return [];
451
-
452
- return items.map((item) => ({
453
- key: `item:${item.id}`,
454
- type: "item" as CandidateType,
455
- id: item.id,
456
- source: opts.source,
457
- text: `${item.subject}: ${item.statement}`,
458
- kind: item.kind,
459
- confidence: item.confidence,
460
- importance: item.importance ?? 0.5,
461
- createdAt: item.lastSeenAt,
462
- lexical: 0,
463
- semantic: 0,
464
- recency: computeRecencyScore(item.lastSeenAt),
465
- finalScore: 0,
466
- }));
467
- }
468
-
469
- /**
470
- * Multi-step typed traversal: each step expands the frontier through
471
- * edges matching the step's relation/entity type filters.
472
- * Returns entity IDs reachable after all steps are applied in sequence.
473
- */
474
- export function collectTypedNeighbors(
475
- seedEntityIds: string[],
476
- steps: TraversalStep[],
477
- opts?: { maxResultsPerStep?: number; maxEdgesPerStep?: number },
478
- ): string[] {
479
- if (seedEntityIds.length === 0 || steps.length === 0) return [];
480
-
481
- const maxResults = opts?.maxResultsPerStep ?? 20;
482
- const maxEdges = opts?.maxEdgesPerStep ?? 40;
483
-
484
- let currentSeeds = seedEntityIds;
485
-
486
- for (const step of steps) {
487
- if (currentSeeds.length === 0) break;
488
-
489
- const result = findNeighborEntities(currentSeeds, {
490
- maxEdges,
491
- maxNeighborEntities: maxResults,
492
- maxDepth: 1,
493
- relationTypes: step.relationTypes,
494
- entityTypes: step.entityTypes,
495
- directed: true,
496
- });
497
-
498
- currentSeeds = result.neighborEntityIds;
499
- }
500
-
501
- return currentSeeds;
502
- }
503
-
504
- /**
505
- * Find entities reachable from ALL given seeds via their respective
506
- * typed traversal steps, then return the intersection.
507
- */
508
- export function intersectReachable(
509
- queries: Array<{
510
- seedEntityIds: string[];
511
- steps: TraversalStep[];
512
- }>,
513
- opts?: { maxResultsPerStep?: number; maxEdgesPerStep?: number },
514
- ): string[] {
515
- if (queries.length === 0) return [];
516
-
517
- const resultSets: Set<string>[] = [];
518
- for (const query of queries) {
519
- const result = collectTypedNeighbors(
520
- query.seedEntityIds,
521
- query.steps,
522
- opts,
523
- );
524
- resultSets.push(new Set(result));
525
- }
526
-
527
- if (resultSets.length === 0) return [];
528
-
529
- // Intersect all sets: keep only entities present in ALL sets
530
- const intersection = [...resultSets[0]].filter((id) =>
531
- resultSets.every((set) => set.has(id)),
532
- );
533
-
534
- return intersection;
535
- }
@@ -1,70 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { buildFTSQuery, expandQueryForFTS } from "./query-expansion.js";
4
-
5
- describe("expandQueryForFTS", () => {
6
- test("extracts meaningful keywords from conversational input", () => {
7
- const result = expandQueryForFTS(
8
- "what did we discuss about the API design?",
9
- );
10
- expect(result).toEqual(["discuss", "API", "design"]);
11
- });
12
-
13
- test("extracts all tokens from technical input (no stop words)", () => {
14
- const result = expandQueryForFTS("React component lifecycle hooks");
15
- expect(result).toEqual(["React", "component", "lifecycle", "hooks"]);
16
- });
17
-
18
- test("returns single keyword as-is", () => {
19
- const result = expandQueryForFTS("authentication");
20
- expect(result).toEqual(["authentication"]);
21
- });
22
-
23
- test("returns empty array for empty input", () => {
24
- expect(expandQueryForFTS("")).toEqual([]);
25
- });
26
-
27
- test("returns empty array for whitespace-only input", () => {
28
- expect(expandQueryForFTS(" ")).toEqual([]);
29
- });
30
-
31
- test("returns empty array for punctuation-only input", () => {
32
- expect(expandQueryForFTS("???")).toEqual([]);
33
- });
34
-
35
- test("returns original tokens when all are stop words", () => {
36
- const result = expandQueryForFTS("what is the");
37
- expect(result).toEqual(["what", "is", "the"]);
38
- });
39
-
40
- test("splits punctuation-delimited words into separate tokens", () => {
41
- const result = expandQueryForFTS("error-handling config.yaml");
42
- expect(result).toEqual(["error", "handling", "config", "yaml"]);
43
- });
44
-
45
- test("normalizes contractions instead of splitting on apostrophes", () => {
46
- const result = expandQueryForFTS("can't we discuss what's happening?");
47
- expect(result).toEqual(["cant", "discuss", "whats", "happening"]);
48
- });
49
- });
50
-
51
- describe("buildFTSQuery", () => {
52
- test("joins multiple keywords with OR", () => {
53
- const result = buildFTSQuery(["API", "design"]);
54
- expect(result).toBe('"API" OR "design"');
55
- });
56
-
57
- test("wraps single keyword in quotes", () => {
58
- const result = buildFTSQuery(["auth"]);
59
- expect(result).toBe('"auth"');
60
- });
61
-
62
- test("strips double-quote characters from keywords", () => {
63
- const result = buildFTSQuery(['say "hello"', "world"]);
64
- expect(result).toBe('"say hello" OR "world"');
65
- });
66
-
67
- test("returns undefined for empty keywords", () => {
68
- expect(buildFTSQuery([])).toBeUndefined();
69
- });
70
- });