@vellumai/assistant 0.6.5 → 0.6.6

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 (443) hide show
  1. package/AGENTS.md +9 -1
  2. package/ARCHITECTURE.md +15 -17
  3. package/Dockerfile +6 -4
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
  5. package/docs/architecture/integrations.md +32 -39
  6. package/docs/architecture/memory.md +25 -30
  7. package/docs/architecture/security.md +7 -6
  8. package/docs/browser-use-architecture-phase2.md +63 -20
  9. package/docs/plugins.md +761 -0
  10. package/examples/plugins/echo/README.md +132 -0
  11. package/examples/plugins/echo/package.json +17 -0
  12. package/examples/plugins/echo/register.ts +187 -0
  13. package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
  14. package/openapi.yaml +212 -68
  15. package/package.json +1 -1
  16. package/src/__tests__/app-compiler.test.ts +57 -0
  17. package/src/__tests__/approval-cascade.test.ts +7 -2
  18. package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
  19. package/src/__tests__/avatar-generator.test.ts +4 -2
  20. package/src/__tests__/bundled-asset.test.ts +6 -6
  21. package/src/__tests__/catalog-cache.test.ts +69 -0
  22. package/src/__tests__/checker.test.ts +459 -171
  23. package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
  24. package/src/__tests__/compaction-events.test.ts +501 -0
  25. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  26. package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
  27. package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
  28. package/src/__tests__/config-model-image-provider.test.ts +110 -0
  29. package/src/__tests__/config-schema.test.ts +22 -9
  30. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
  31. package/src/__tests__/contacts-tools.test.ts +26 -0
  32. package/src/__tests__/context-overflow-policy.test.ts +7 -7
  33. package/src/__tests__/context-window-manager.test.ts +355 -4
  34. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  35. package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
  36. package/src/__tests__/conversation-agent-loop.test.ts +30 -141
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
  38. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
  40. package/src/__tests__/conversation-pairing.test.ts +174 -10
  41. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
  42. package/src/__tests__/conversation-process-callsite.test.ts +3 -0
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
  44. package/src/__tests__/conversation-queue.test.ts +29 -14
  45. package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
  48. package/src/__tests__/conversation-seed-composer.test.ts +2 -2
  49. package/src/__tests__/conversation-slash-queue.test.ts +7 -2
  50. package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
  51. package/src/__tests__/conversation-speed-override.test.ts +6 -1
  52. package/src/__tests__/conversation-title-service.test.ts +116 -0
  53. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
  54. package/src/__tests__/conversation-usage.test.ts +1 -1
  55. package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
  56. package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
  57. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
  58. package/src/__tests__/credential-health-service.test.ts +78 -9
  59. package/src/__tests__/credential-security-invariants.test.ts +2 -2
  60. package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
  61. package/src/__tests__/empty-response-pipeline.test.ts +305 -0
  62. package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
  63. package/src/__tests__/first-greeting.test.ts +247 -5
  64. package/src/__tests__/headless-browser-mode.test.ts +57 -0
  65. package/src/__tests__/history-repair-pipeline.test.ts +399 -0
  66. package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
  67. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
  68. package/src/__tests__/host-proxy-interface.test.ts +36 -2
  69. package/src/__tests__/image-credentials.test.ts +137 -0
  70. package/src/__tests__/image-service-dispatcher.test.ts +186 -0
  71. package/src/__tests__/injector-chain.test.ts +526 -0
  72. package/src/__tests__/intent-routing.test.ts +0 -26
  73. package/src/__tests__/llm-call-pipeline.test.ts +285 -0
  74. package/src/__tests__/llm-schema.test.ts +1 -1
  75. package/src/__tests__/media-generate-image.test.ts +119 -13
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
  77. package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
  78. package/src/__tests__/migration-import-from-url.test.ts +5 -68
  79. package/src/__tests__/model-intents.test.ts +4 -2
  80. package/src/__tests__/notification-broadcaster.test.ts +3 -3
  81. package/src/__tests__/notification-decision-strategy.test.ts +0 -11
  82. package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
  83. package/src/__tests__/oauth-apps-routes.test.ts +1 -1
  84. package/src/__tests__/oauth-cli.test.ts +14 -12
  85. package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
  86. package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
  87. package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
  88. package/src/__tests__/oauth-providers-routes.test.ts +3 -2
  89. package/src/__tests__/oauth-store.test.ts +41 -76
  90. package/src/__tests__/onboarding-template-contract.test.ts +16 -64
  91. package/src/__tests__/openai-image-service.test.ts +368 -0
  92. package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
  93. package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
  94. package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
  95. package/src/__tests__/persistence-pipeline.test.ts +377 -0
  96. package/src/__tests__/pipeline-runner.test.ts +565 -0
  97. package/src/__tests__/platform.test.ts +5 -2
  98. package/src/__tests__/plugin-bootstrap.test.ts +483 -0
  99. package/src/__tests__/plugin-registry.test.ts +273 -0
  100. package/src/__tests__/plugin-route-contribution.test.ts +288 -0
  101. package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
  102. package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
  103. package/src/__tests__/plugin-types.test.ts +320 -0
  104. package/src/__tests__/pricing.test.ts +44 -12
  105. package/src/__tests__/proxy-approval-callback.test.ts +69 -8
  106. package/src/__tests__/reaction-persistence.test.ts +1 -0
  107. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  108. package/src/__tests__/registry.test.ts +0 -2
  109. package/src/__tests__/schedule-routes.test.ts +131 -1
  110. package/src/__tests__/scheduler-recurrence.test.ts +14 -70
  111. package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
  112. package/src/__tests__/secret-detection-handler.test.ts +0 -10
  113. package/src/__tests__/shell-identity.test.ts +0 -134
  114. package/src/__tests__/suggestion-routes.test.ts +103 -4
  115. package/src/__tests__/task-memory-cleanup.test.ts +1 -0
  116. package/src/__tests__/task-scheduler.test.ts +3 -15
  117. package/src/__tests__/test-preload.ts +11 -0
  118. package/src/__tests__/title-generate-pipeline.test.ts +224 -0
  119. package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
  120. package/src/__tests__/tool-error-pipeline.test.ts +244 -0
  121. package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
  122. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
  123. package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
  124. package/src/__tests__/tool-executor.test.ts +141 -0
  125. package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
  126. package/src/__tests__/tool-result-truncation.test.ts +0 -110
  127. package/src/__tests__/user-plugin-loader.test.ts +191 -0
  128. package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
  129. package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
  130. package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
  131. package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
  132. package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
  133. package/src/__tests__/workspace-policy.test.ts +21 -3
  134. package/src/agent/loop.ts +340 -102
  135. package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
  136. package/src/approvals/guardian-request-resolvers.ts +80 -0
  137. package/src/backup/__tests__/backup-worker.test.ts +2 -13
  138. package/src/backup/backup-worker.ts +3 -15
  139. package/src/bundler/app-compiler.ts +84 -1
  140. package/src/calls/call-state.ts +2 -2
  141. package/src/channels/__tests__/types.test.ts +3 -3
  142. package/src/channels/types.ts +6 -4
  143. package/src/cli/__tests__/notifications.test.ts +87 -211
  144. package/src/cli/commands/__tests__/backup.test.ts +1 -1
  145. package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
  146. package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
  147. package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
  148. package/src/cli/commands/backup.ts +2 -2
  149. package/src/cli/commands/clients.ts +138 -0
  150. package/src/cli/commands/completions.ts +2 -9
  151. package/src/cli/commands/conversations.ts +55 -7
  152. package/src/cli/commands/image-generation.ts +33 -34
  153. package/src/cli/commands/notifications.ts +68 -103
  154. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
  155. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  156. package/src/cli/commands/oauth/connect.ts +2 -2
  157. package/src/cli/commands/oauth/providers.ts +176 -8
  158. package/src/cli/commands/oauth/status.ts +46 -36
  159. package/src/cli/commands/skills.ts +3 -4
  160. package/src/cli/program.ts +25 -29
  161. package/src/config/__tests__/backup-schema.test.ts +7 -2
  162. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  163. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
  164. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
  165. package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
  166. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
  170. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  171. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
  172. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
  174. package/src/config/bundled-skills/schedule/SKILL.md +8 -3
  175. package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
  176. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
  177. package/src/config/bundled-tool-registry.ts +0 -15
  178. package/src/config/feature-flag-registry.json +17 -1
  179. package/src/config/schema.ts +19 -0
  180. package/src/config/schemas/backup.ts +1 -1
  181. package/src/config/schemas/conversations.ts +16 -0
  182. package/src/config/schemas/llm.ts +2 -3
  183. package/src/config/schemas/security.ts +6 -6
  184. package/src/config/schemas/tts.ts +11 -0
  185. package/src/config/skill-state.ts +6 -2
  186. package/src/config/skills.ts +94 -5
  187. package/src/context/__tests__/compact-prompt.test.ts +27 -9
  188. package/src/context/prompts/compact.md +26 -12
  189. package/src/context/tool-result-truncation.ts +3 -63
  190. package/src/context/window-manager.ts +190 -16
  191. package/src/credential-health/credential-health-service.ts +19 -6
  192. package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
  193. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
  194. package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
  195. package/src/daemon/config-watcher.ts +0 -2
  196. package/src/daemon/context-overflow-policy.ts +4 -13
  197. package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
  198. package/src/daemon/conversation-agent-loop.ts +984 -683
  199. package/src/daemon/conversation-history.ts +10 -19
  200. package/src/daemon/conversation-lifecycle.ts +37 -19
  201. package/src/daemon/conversation-notifiers.ts +2 -110
  202. package/src/daemon/conversation-process.ts +14 -7
  203. package/src/daemon/conversation-runtime-assembly.ts +532 -411
  204. package/src/daemon/conversation-tool-setup.ts +41 -4
  205. package/src/daemon/conversation.ts +80 -35
  206. package/src/daemon/external-plugins-bootstrap.ts +478 -0
  207. package/src/daemon/first-greeting.ts +191 -14
  208. package/src/daemon/handlers/config-model.ts +11 -0
  209. package/src/daemon/handlers/skills.ts +5 -1
  210. package/src/daemon/lifecycle.ts +33 -68
  211. package/src/daemon/message-types/computer-use.ts +2 -34
  212. package/src/daemon/message-types/conversations.ts +49 -0
  213. package/src/daemon/message-types/messages.ts +12 -0
  214. package/src/daemon/server.ts +5 -3
  215. package/src/daemon/shutdown-handlers.ts +2 -12
  216. package/src/daemon/tool-side-effects.ts +14 -56
  217. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
  218. package/src/heartbeat/heartbeat-service.ts +24 -1
  219. package/src/home/__tests__/feed-population-integration.test.ts +312 -0
  220. package/src/home/emit-feed-event.ts +7 -0
  221. package/src/home/feed-types.ts +41 -2
  222. package/src/home/rewrite-command-preview.ts +66 -0
  223. package/src/ipc/__tests__/socket-path.test.ts +11 -50
  224. package/src/ipc/cli-client.ts +1 -1
  225. package/src/ipc/cli-server.ts +3 -3
  226. package/src/ipc/gateway-client.ts +4 -1
  227. package/src/ipc/routes/browser-context.ts +2 -0
  228. package/src/ipc/routes/browser.ts +1 -0
  229. package/src/ipc/routes/get-contact.ts +16 -0
  230. package/src/ipc/routes/index.ts +14 -0
  231. package/src/ipc/routes/list-clients.ts +31 -0
  232. package/src/ipc/routes/merge-contacts.ts +17 -0
  233. package/src/ipc/routes/notification.ts +133 -0
  234. package/src/ipc/routes/rename-conversation.ts +59 -0
  235. package/src/ipc/routes/search-contacts.ts +19 -0
  236. package/src/ipc/routes/upsert-contact.ts +25 -0
  237. package/src/ipc/socket-path.ts +14 -38
  238. package/src/media/app-icon-generator.ts +23 -46
  239. package/src/media/avatar-router.ts +26 -41
  240. package/src/media/gemini-image-service.ts +8 -41
  241. package/src/media/image-credentials.ts +73 -0
  242. package/src/media/image-service.ts +85 -0
  243. package/src/media/openai-image-service.ts +131 -0
  244. package/src/media/types.ts +46 -0
  245. package/src/memory/conversation-crud.ts +48 -18
  246. package/src/memory/conversation-queries.ts +57 -4
  247. package/src/memory/conversation-title-service.ts +25 -0
  248. package/src/memory/db-init.ts +8 -0
  249. package/src/memory/embedding-gemini.test.ts +41 -2
  250. package/src/memory/embedding-gemini.ts +6 -1
  251. package/src/memory/graph/bootstrap.test.ts +282 -0
  252. package/src/memory/graph/bootstrap.ts +8 -5
  253. package/src/memory/graph/extraction.ts +10 -2
  254. package/src/memory/graph/graph-search.test.ts +1 -0
  255. package/src/memory/graph/inspect.ts +2 -2
  256. package/src/memory/graph/retriever.ts +10 -3
  257. package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
  258. package/src/memory/migrations/149-oauth-tables.ts +1 -0
  259. package/src/memory/migrations/223-schedule-script-column.ts +11 -0
  260. package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
  261. package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
  262. package/src/memory/migrations/index.ts +4 -0
  263. package/src/memory/pkb/pkb-index.test.ts +1 -0
  264. package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
  265. package/src/memory/pkb/pkb-search.test.ts +65 -4
  266. package/src/memory/pkb/pkb-search.ts +40 -18
  267. package/src/memory/qdrant-client.test.ts +60 -0
  268. package/src/memory/qdrant-client.ts +25 -0
  269. package/src/memory/schema/infrastructure.ts +1 -0
  270. package/src/memory/schema/oauth.ts +4 -1
  271. package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
  272. package/src/messaging/providers/slack/render-transcript.ts +58 -0
  273. package/src/notifications/conversation-pairing.ts +78 -19
  274. package/src/notifications/copy-composer.ts +0 -5
  275. package/src/notifications/emit-signal.ts +1 -1
  276. package/src/notifications/signal.ts +1 -2
  277. package/src/oauth/AGENTS.md +1 -1
  278. package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
  279. package/src/oauth/connect-orchestrator.ts +8 -34
  280. package/src/oauth/connect-types.ts +6 -10
  281. package/src/oauth/manual-token-connection.ts +23 -0
  282. package/src/oauth/oauth-store.ts +30 -14
  283. package/src/oauth/provider-serializer.ts +6 -1
  284. package/src/oauth/seed-providers.ts +56 -108
  285. package/src/outbound-proxy/http-forwarder.ts +9 -0
  286. package/src/permissions/approval-policy.test.ts +293 -18
  287. package/src/permissions/approval-policy.ts +110 -58
  288. package/src/permissions/arg-parser.test.ts +161 -0
  289. package/src/permissions/arg-parser.ts +141 -0
  290. package/src/permissions/bash-risk-classifier.test.ts +414 -2
  291. package/src/permissions/bash-risk-classifier.ts +303 -60
  292. package/src/permissions/checker.ts +157 -29
  293. package/src/permissions/command-registry.test.ts +239 -0
  294. package/src/permissions/command-registry.ts +234 -54
  295. package/src/permissions/defaults.ts +5 -4
  296. package/src/permissions/gateway-threshold-reader.ts +196 -0
  297. package/src/permissions/prompter.ts +4 -0
  298. package/src/permissions/risk-types.ts +61 -4
  299. package/src/permissions/schedule-risk-classifier.test.ts +129 -0
  300. package/src/permissions/schedule-risk-classifier.ts +85 -0
  301. package/src/permissions/shell-identity.ts +2 -42
  302. package/src/permissions/types.ts +2 -0
  303. package/src/permissions/workspace-policy.ts +8 -3
  304. package/src/plugins/defaults/circuit-breaker.ts +146 -0
  305. package/src/plugins/defaults/compaction.ts +145 -0
  306. package/src/plugins/defaults/empty-response.ts +126 -0
  307. package/src/plugins/defaults/history-repair.ts +85 -0
  308. package/src/plugins/defaults/index.ts +116 -0
  309. package/src/plugins/defaults/injectors.ts +491 -0
  310. package/src/plugins/defaults/llm-call.ts +82 -0
  311. package/src/plugins/defaults/memory-retrieval.ts +226 -0
  312. package/src/plugins/defaults/overflow-reduce.ts +181 -0
  313. package/src/plugins/defaults/persistence.ts +129 -0
  314. package/src/plugins/defaults/title-generate.ts +95 -0
  315. package/src/plugins/defaults/token-estimate.ts +104 -0
  316. package/src/plugins/defaults/tool-error.ts +126 -0
  317. package/src/plugins/defaults/tool-execute.ts +89 -0
  318. package/src/plugins/defaults/tool-result-truncate.ts +88 -0
  319. package/src/plugins/pipeline.ts +316 -0
  320. package/src/plugins/plugin-skill-contributions.ts +292 -0
  321. package/src/plugins/registry.ts +241 -0
  322. package/src/plugins/types.ts +1134 -0
  323. package/src/plugins/user-loader.ts +177 -0
  324. package/src/prompts/templates/BOOTSTRAP.md +27 -77
  325. package/src/providers/model-catalog.ts +52 -29
  326. package/src/providers/model-intents.ts +1 -1
  327. package/src/providers/openrouter/client.ts +5 -1
  328. package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
  329. package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
  330. package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
  331. package/src/providers/speech-to-text/xai-realtime.ts +39 -14
  332. package/src/runtime/AGENTS.md +25 -16
  333. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
  334. package/src/runtime/__tests__/client-registry.test.ts +293 -0
  335. package/src/runtime/client-registry.ts +261 -0
  336. package/src/runtime/http-server.ts +77 -8
  337. package/src/runtime/http-types.ts +0 -2
  338. package/src/runtime/migrations/vbundle-builder.ts +1 -22
  339. package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
  340. package/src/runtime/routes/approval-routes.ts +17 -0
  341. package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
  342. package/src/runtime/routes/conversation-routes.ts +223 -116
  343. package/src/runtime/routes/inbound-message-handler.ts +88 -13
  344. package/src/runtime/routes/memory-item-routes.test.ts +1 -0
  345. package/src/runtime/routes/migration-routes.ts +0 -3
  346. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
  347. package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
  348. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
  349. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
  350. package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
  351. package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
  352. package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
  353. package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
  354. package/src/runtime/routes/playground/deps.ts +56 -0
  355. package/src/runtime/routes/playground/force-compact.ts +73 -0
  356. package/src/runtime/routes/playground/guard.ts +37 -0
  357. package/src/runtime/routes/playground/index.ts +28 -0
  358. package/src/runtime/routes/playground/inject-failures.ts +159 -0
  359. package/src/runtime/routes/playground/reset-circuit.ts +115 -0
  360. package/src/runtime/routes/playground/seed-conversation.ts +139 -0
  361. package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
  362. package/src/runtime/routes/playground/state.ts +78 -0
  363. package/src/runtime/routes/schedule-routes.ts +89 -8
  364. package/src/runtime/skill-route-registry.ts +75 -15
  365. package/src/schedule/run-script.ts +68 -0
  366. package/src/schedule/schedule-store.ts +7 -1
  367. package/src/schedule/scheduler.ts +48 -8
  368. package/src/skills/catalog-cache.ts +12 -5
  369. package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
  370. package/src/tools/browser/browser-execution.ts +88 -19
  371. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
  372. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
  373. package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
  374. package/src/tools/browser/cdp-client/factory.ts +15 -4
  375. package/src/tools/executor.ts +126 -74
  376. package/src/tools/network/script-proxy/session-manager.ts +37 -1
  377. package/src/tools/permission-checker.ts +98 -49
  378. package/src/tools/policy-context.ts +4 -0
  379. package/src/tools/registry.ts +140 -3
  380. package/src/tools/schedule/create.ts +23 -8
  381. package/src/tools/schedule/update.ts +3 -1
  382. package/src/tools/secret-detection-handler.ts +0 -51
  383. package/src/tools/system/avatar-generator.ts +6 -2
  384. package/src/tools/types.ts +28 -2
  385. package/src/util/platform.ts +7 -2
  386. package/src/util/pricing.ts +26 -3
  387. package/src/workspace/migrations/006-services-config.ts +2 -4
  388. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
  389. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
  390. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
  391. package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
  392. package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
  393. package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
  394. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
  395. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
  396. package/src/workspace/migrations/registry.ts +12 -0
  397. package/tsconfig.json +1 -1
  398. package/hook-templates/debug-prompt-logger/hook.json +0 -7
  399. package/hook-templates/debug-prompt-logger/run.sh +0 -66
  400. package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
  401. package/src/__tests__/context-overflow-approval.test.ts +0 -156
  402. package/src/__tests__/hooks-blocking.test.ts +0 -178
  403. package/src/__tests__/hooks-cli.test.ts +0 -182
  404. package/src/__tests__/hooks-config.test.ts +0 -108
  405. package/src/__tests__/hooks-discovery.test.ts +0 -211
  406. package/src/__tests__/hooks-integration.test.ts +0 -196
  407. package/src/__tests__/hooks-manager.test.ts +0 -226
  408. package/src/__tests__/hooks-runner.test.ts +0 -175
  409. package/src/__tests__/hooks-settings.test.ts +0 -160
  410. package/src/__tests__/hooks-templates.test.ts +0 -169
  411. package/src/__tests__/hooks-ts-runner.test.ts +0 -170
  412. package/src/__tests__/hooks-watch.test.ts +0 -112
  413. package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
  414. package/src/__tests__/oauth-scope-policy.test.ts +0 -180
  415. package/src/__tests__/send-notification-tool.test.ts +0 -83
  416. package/src/cli/commands/shotgun.ts +0 -266
  417. package/src/config/bundled-skills/conversations/SKILL.md +0 -20
  418. package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
  419. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
  420. package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
  421. package/src/config/bundled-skills/notifications/SKILL.md +0 -40
  422. package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
  423. package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
  424. package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
  425. package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
  426. package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
  427. package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
  428. package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
  429. package/src/daemon/context-overflow-approval.ts +0 -52
  430. package/src/daemon/watch-handler.ts +0 -399
  431. package/src/hooks/cli.ts +0 -253
  432. package/src/hooks/config.ts +0 -100
  433. package/src/hooks/discovery.ts +0 -135
  434. package/src/hooks/manager.ts +0 -179
  435. package/src/hooks/runner.ts +0 -117
  436. package/src/hooks/templates.ts +0 -77
  437. package/src/hooks/types.ts +0 -75
  438. package/src/oauth/scope-policy.ts +0 -89
  439. package/src/runtime/gateway-internal-client.ts +0 -94
  440. package/src/runtime/routes/watch-routes.ts +0 -156
  441. package/src/signals/shotgun.ts +0 -203
  442. package/src/tools/watch/screen-watch.ts +0 -144
  443. package/src/tools/watch/watch-state.ts +0 -142
@@ -21,7 +21,7 @@ import { getWorkspaceDir } from "../../util/platform.js";
21
21
  import { getMemoryCheckpoint, setMemoryCheckpoint } from "../checkpoints.js";
22
22
  import { getDb, rawAll, rawGet, rawRun } from "../db.js";
23
23
  import { enqueueMemoryJob, hasActiveJobOfType } from "../jobs-store.js";
24
- import { initQdrantClient } from "../qdrant-client.js";
24
+ import { initQdrantClient, resolveQdrantUrl } from "../qdrant-client.js";
25
25
  import { conversations, memoryGraphNodes, memorySegments } from "../schema.js";
26
26
  import { runGraphExtraction } from "./extraction.js";
27
27
  import { countNodes } from "./store.js";
@@ -71,7 +71,7 @@ export async function bootstrapFromHistory(
71
71
  // Initialize Qdrant client for inline embedding
72
72
  try {
73
73
  initQdrantClient({
74
- url: config.memory.qdrant.url ?? "http://127.0.0.1:6333",
74
+ url: resolveQdrantUrl(config),
75
75
  collection: config.memory.qdrant.collection,
76
76
  vectorSize: config.memory.qdrant.vectorSize,
77
77
  onDisk: config.memory.qdrant.onDisk ?? true,
@@ -384,6 +384,10 @@ const KIND_TO_PREFIX: Record<string, string> = {
384
384
  * schema. ORM-based inserts include every column in the schema definition,
385
385
  * so adding a column in a later migration would cause this migration to
386
386
  * fail with "table has no column named …" on upgrade paths.
387
+ *
388
+ * The INSERT intentionally omits columns added by later migrations (e.g.
389
+ * `image_refs` from migration 205) since they default to NULL and
390
+ * including them would couple this migration to those later schema changes.
387
391
  */
388
392
  export function migrateToolCreatedItems(): void {
389
393
  if (getMemoryCheckpoint(MIGRATE_ITEMS_CHECKPOINT)) return;
@@ -446,8 +450,8 @@ export function migrateToolCreatedItems(): void {
446
450
  event_date, emotional_charge, fidelity, confidence, significance,
447
451
  stability, reinforcement_count, last_reinforced,
448
452
  source_conversations, source_type, narrative_role, part_of_story,
449
- image_refs, scope_id
450
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
453
+ scope_id
454
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
451
455
  id,
452
456
  content,
453
457
  "semantic",
@@ -466,7 +470,6 @@ export function migrateToolCreatedItems(): void {
466
470
  "direct",
467
471
  null,
468
472
  null,
469
- null,
470
473
  row.scope_id || "default",
471
474
  );
472
475
 
@@ -1066,10 +1066,18 @@ export async function runGraphExtraction(
1066
1066
  );
1067
1067
 
1068
1068
  // 7. Handle supersession (inherit durability before applying diff)
1069
+ // TODO: full supersession is not yet implemented. When it lands, iterate
1070
+ // BOTH `diff.createEdges` (existing → existing) AND `deferredEdges`
1071
+ // (new → existing, the typical supersession case).
1072
+ // Tracked by https://github.com/vellum-ai/vellum-assistant/pull/27057 (Devin).
1069
1073
  for (const edge of diff.createEdges) {
1070
1074
  if (edge.relationship === "supersedes") {
1071
- // Supersession is handled differently — see supersedeNode in store
1072
- // For now, just mark it; full supersession is applied after node creation
1075
+ // Placeholder — see TODO above.
1076
+ }
1077
+ }
1078
+ for (const de of deferredEdges) {
1079
+ if (de.relationship === "supersedes") {
1080
+ // Placeholder — see TODO above.
1073
1081
  }
1074
1082
  }
1075
1083
 
@@ -49,6 +49,7 @@ mock.module("../qdrant-client.js", () => ({
49
49
  },
50
50
  }),
51
51
  initQdrantClient: () => {},
52
+ resolveQdrantUrl: () => "http://127.0.0.1:6333",
52
53
  VellumQdrantClient: class {},
53
54
  }));
54
55
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  import { getConfig } from "../../config/loader.js";
16
16
  import { initializeDb } from "../db-init.js";
17
- import { initQdrantClient } from "../qdrant-client.js";
17
+ import { initQdrantClient, resolveQdrantUrl } from "../qdrant-client.js";
18
18
  import {
19
19
  countNodes,
20
20
  getEdgesForNode,
@@ -29,7 +29,7 @@ initializeDb();
29
29
  const config = getConfig();
30
30
  try {
31
31
  initQdrantClient({
32
- url: config.memory.qdrant.url ?? "http://127.0.0.1:6333",
32
+ url: resolveQdrantUrl(config),
33
33
  collection: config.memory.qdrant.collection,
34
34
  vectorSize: config.memory.qdrant.vectorSize,
35
35
  onDisk: config.memory.qdrant.onDisk ?? true,
@@ -877,9 +877,16 @@ export interface TurnRetrievalResult {
877
877
  */
878
878
  queryVector?: number[];
879
879
  /**
880
- * Optional sparse vector passed alongside `queryVector`. Currently always
881
- * `undefined` reserved for future hybrid retrieval that produces a
882
- * sparse vector at the call site.
880
+ * Sparse (TF-IDF) vector of the user's last message, computed once per
881
+ * turn and reused across every return path of `retrieveForTurn`. Surfaced
882
+ * so downstream callers (e.g. the PKB hint retriever in
883
+ * `applyRuntimeInjections`) can pair it with `queryVector` to run a
884
+ * hybrid dense+sparse query — RRF fusion pulls in lexical matches
885
+ * (exact filenames, proper nouns, uncommon tokens) that pure dense
886
+ * embeddings wash out. Computed locally (no embedding-service call), so
887
+ * it survives even when the dense embed fails via the circuit breaker.
888
+ * `undefined` when the user's last message is empty/whitespace-only or
889
+ * yields no TF-IDF tokens.
883
890
  */
884
891
  sparseVector?: QdrantSparseVector;
885
892
  }
@@ -0,0 +1,26 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+
3
+ /**
4
+ * Tracker for approval prompt message timestamps.
5
+ *
6
+ * Stores the (channel, chat_id, ts) tuples for delivered guardian approval
7
+ * prompts so only reactions on a known prompt can resolve a pending
8
+ * request. Persistence (rather than in-memory state) is required because
9
+ * the guardian approval TTL is 30 minutes, which can span a daemon
10
+ * restart between prompt delivery and the user's reaction.
11
+ */
12
+ export function createApprovalPromptTsTrackerTable(database: DrizzleDb): void {
13
+ database.run(/*sql*/ `
14
+ CREATE TABLE IF NOT EXISTS approval_prompt_ts_tracker (
15
+ channel TEXT NOT NULL,
16
+ chat_id TEXT NOT NULL,
17
+ ts TEXT NOT NULL,
18
+ expires_at INTEGER NOT NULL,
19
+ PRIMARY KEY (channel, chat_id, ts)
20
+ )
21
+ `);
22
+
23
+ database.run(
24
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_approval_prompt_ts_tracker_expires ON approval_prompt_ts_tracker(expires_at)`,
25
+ );
26
+ }
@@ -15,6 +15,7 @@ export function createOAuthTables(database: DrizzleDb): void {
15
15
  base_url TEXT,
16
16
  default_scopes TEXT NOT NULL DEFAULT '[]',
17
17
  scope_policy TEXT NOT NULL DEFAULT '{}',
18
+ available_scopes TEXT,
18
19
  extra_params TEXT,
19
20
  callback_transport TEXT,
20
21
  loopback_port INTEGER,
@@ -0,0 +1,11 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ export function migrateScheduleScriptColumn(database: DrizzleDb): void {
5
+ const raw = getSqliteFrom(database);
6
+ try {
7
+ raw.exec(`ALTER TABLE cron_jobs ADD COLUMN script TEXT`);
8
+ } catch {
9
+ // Column already exists — nothing to do.
10
+ }
11
+ }
@@ -0,0 +1,24 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ /**
5
+ * Add the `managed_service_is_paid` column to `oauth_providers`.
6
+ *
7
+ * Flags whether a provider's managed OAuth variant requires a paid plan
8
+ * upstream (e.g. Twitter). Defaults to 0 (false) for all existing rows.
9
+ *
10
+ * Mirrors the pattern from migration 178. The raw.exec() call below is
11
+ * Drizzle's SQLite statement runner — NOT a shell exec.
12
+ */
13
+ export function migrateOAuthProvidersManagedServiceIsPaid(
14
+ database: DrizzleDb,
15
+ ): void {
16
+ const raw = getSqliteFrom(database);
17
+ try {
18
+ raw.exec(
19
+ /*sql*/ `ALTER TABLE oauth_providers ADD COLUMN managed_service_is_paid INTEGER NOT NULL DEFAULT 0`,
20
+ );
21
+ } catch {
22
+ // Column already exists — nothing to do.
23
+ }
24
+ }
@@ -0,0 +1,13 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ export function migrateOAuthProvidersAvailableScopes(
5
+ database: DrizzleDb,
6
+ ): void {
7
+ const raw = getSqliteFrom(database);
8
+ try {
9
+ raw.exec(`ALTER TABLE oauth_providers ADD COLUMN available_scopes TEXT`);
10
+ } catch {
11
+ // Column already exists — nothing to do.
12
+ }
13
+ }
@@ -42,6 +42,7 @@ export { migrateVoiceInviteColumns } from "./037-voice-invite-columns.js";
42
42
  export { createActorTokenRecordsTable } from "./038-actor-token-records.js";
43
43
  export { createActorRefreshTokenRecordsTable } from "./039-actor-refresh-token-records.js";
44
44
  export { migrateInviteCodeHashColumn } from "./040-invite-code-hash-column.js";
45
+ export { createApprovalPromptTsTrackerTable } from "./041-approval-prompt-ts-tracker.js";
45
46
  export { createCoreTables } from "./100-core-tables.js";
46
47
  export { createWatchersAndLogsTables } from "./101-watchers-and-logs.js";
47
48
  export { addCoreColumns } from "./102-alter-table-columns.js";
@@ -167,6 +168,9 @@ export {
167
168
  } from "./220-normalize-user-file-by-principal.js";
168
169
  export { migrateConversationsArchivedAt } from "./221-conversations-archived-at.js";
169
170
  export { migrateStripPlaceholderSentinelsFromMessages } from "./222-strip-placeholder-sentinels-from-messages.js";
171
+ export { migrateScheduleScriptColumn } from "./223-schedule-script-column.js";
172
+ export { migrateOAuthProvidersManagedServiceIsPaid } from "./224-oauth-providers-managed-service-is-paid.js";
173
+ export { migrateOAuthProvidersAvailableScopes } from "./225-oauth-providers-available-scopes.js";
170
174
  export {
171
175
  MIGRATION_REGISTRY,
172
176
  type MigrationRegistryEntry,
@@ -96,6 +96,7 @@ mock.module("../qdrant-client.js", () => ({
96
96
  return scrollReturnPoints;
97
97
  },
98
98
  }),
99
+ resolveQdrantUrl: () => "http://127.0.0.1:6333",
99
100
  }));
100
101
 
101
102
  // The circuit breaker is a thin wrapper; just call the function through.
@@ -46,6 +46,7 @@ mock.module("../qdrant-client.js", () => ({
46
46
  deleteCalls.push({ path, memoryScopeId });
47
47
  },
48
48
  }),
49
+ resolveQdrantUrl: () => "http://127.0.0.1:6333",
49
50
  }));
50
51
 
51
52
  // Circuit breaker — pass-through.
@@ -25,6 +25,8 @@ type ScoredPoint = { id: string; score: number; payload: Payload };
25
25
 
26
26
  let hybridResults: ScoredPoint[] = [];
27
27
  let denseResults: ScoredPoint[] = [];
28
+ let hybridThrows: Error | null = null;
29
+ let denseThrows: Error | null = null;
28
30
 
29
31
  mock.module("../qdrant-circuit-breaker.js", () => ({
30
32
  isQdrantBreakerOpen: () => breakerOpen,
@@ -41,6 +43,7 @@ mock.module("../qdrant-client.js", () => ({
41
43
  prefetchLimit?: number;
42
44
  }) => {
43
45
  hybridSearchCalls.push(params);
46
+ if (hybridThrows) throw hybridThrows;
44
47
  return hybridResults;
45
48
  },
46
49
  search: async (
@@ -49,9 +52,11 @@ mock.module("../qdrant-client.js", () => ({
49
52
  filter?: Record<string, unknown>,
50
53
  ) => {
51
54
  searchCalls.push({ vector, limit, filter });
55
+ if (denseThrows) throw denseThrows;
52
56
  return denseResults;
53
57
  },
54
58
  }),
59
+ resolveQdrantUrl: () => "http://127.0.0.1:6333",
55
60
  }));
56
61
 
57
62
  const { searchPkbFiles } = await import("./pkb-search.js");
@@ -63,6 +68,8 @@ describe("searchPkbFiles", () => {
63
68
  searchCalls.length = 0;
64
69
  hybridResults = [];
65
70
  denseResults = [];
71
+ hybridThrows = null;
72
+ denseThrows = null;
66
73
  });
67
74
 
68
75
  test("filter payload targets pkb_file (hybrid path runs both queries)", async () => {
@@ -236,7 +243,7 @@ describe("searchPkbFiles", () => {
236
243
  expect(other?.hybridScore).toBeUndefined();
237
244
  });
238
245
 
239
- test("result present only in hybrid returns denseScore=0 and a hybridScore", async () => {
246
+ test("hybrid-only hits (no dense match) are dropped so they can't evict dense-qualified paths before the slice", async () => {
240
247
  denseResults = [
241
248
  {
242
249
  id: "a",
@@ -248,6 +255,10 @@ describe("searchPkbFiles", () => {
248
255
  },
249
256
  },
250
257
  ];
258
+ // `b` appears only in the hybrid response (outside the dense prefetch).
259
+ // It has no cosine score, so it can never pass a downstream threshold
260
+ // and must not be surfaced — otherwise it could crowd out dense-qualified
261
+ // hits before the caller gates on denseScore.
251
262
  hybridResults = [
252
263
  {
253
264
  id: "a",
@@ -275,12 +286,62 @@ describe("searchPkbFiles", () => {
275
286
  10,
276
287
  );
277
288
 
289
+ expect(results).toHaveLength(1);
278
290
  const a = results.find((r) => r.path === "/notes/a.md");
279
- const b = results.find((r) => r.path === "/notes/b.md");
280
291
  expect(a?.denseScore).toBe(0.8);
281
292
  expect(a?.hybridScore).toBe(0.03);
282
- expect(b?.denseScore).toBe(0);
283
- expect(b?.hybridScore).toBe(0.02);
293
+ expect(results.find((r) => r.path === "/notes/b.md")).toBeUndefined();
294
+ });
295
+
296
+ test("hybrid failure (transient) falls back to dense-only results", async () => {
297
+ denseResults = [
298
+ {
299
+ id: "a",
300
+ score: 0.8,
301
+ payload: {
302
+ target_type: "pkb_file",
303
+ target_id: "t-1",
304
+ path: "/notes/a.md",
305
+ },
306
+ },
307
+ ];
308
+ hybridThrows = new Error("qdrant hybrid transient failure");
309
+
310
+ const results = await searchPkbFiles(
311
+ [0.1],
312
+ { indices: [1], values: [1] },
313
+ 10,
314
+ );
315
+
316
+ expect(results).toHaveLength(1);
317
+ expect(results[0]?.path).toBe("/notes/a.md");
318
+ expect(results[0]?.denseScore).toBe(0.8);
319
+ expect(results[0]?.hybridScore).toBeUndefined();
320
+ });
321
+
322
+ test("dense failure (transient) falls back to empty results even if hybrid succeeded", async () => {
323
+ denseThrows = new Error("qdrant dense transient failure");
324
+ hybridResults = [
325
+ {
326
+ id: "a",
327
+ score: 0.03,
328
+ payload: {
329
+ target_type: "pkb_file",
330
+ target_id: "t-1",
331
+ path: "/notes/a.md",
332
+ },
333
+ },
334
+ ];
335
+
336
+ // Without a dense cosine score there is nothing to gate on, so the
337
+ // hybrid-only fallback surfaces no results.
338
+ const results = await searchPkbFiles(
339
+ [0.1],
340
+ { indices: [1], values: [1] },
341
+ 10,
342
+ );
343
+
344
+ expect(results).toEqual([]);
284
345
  });
285
346
 
286
347
  test("empty Qdrant response yields []", async () => {
@@ -28,9 +28,10 @@ const log = getLogger("pkb-search");
28
28
  *
29
29
  * PKB files are chunked at index time, so a single path can match on
30
30
  * multiple chunks. Scores collapse to the highest per path, per query.
31
- * The two queries are unioned by path a path present in only one
32
- * response still surfaces, with the missing score left `undefined` on
33
- * its result.
31
+ * Only paths with a dense cosine score are returned (hybrid-only matches
32
+ * are dropped because their implicit `denseScore = 0` can never satisfy a
33
+ * caller's threshold, and keeping them would evict dense-qualifying hits
34
+ * from the pre-slice top-`limit` window).
34
35
  */
35
36
  export async function searchPkbFiles(
36
37
  queryVector: number[],
@@ -77,29 +78,50 @@ export async function searchPkbFiles(
77
78
  )
78
79
  : Promise.resolve([]);
79
80
 
80
- const [denseResults, hybridResults] = await Promise.all([
81
- densePromise,
82
- hybridPromise,
83
- ]);
81
+ // Silence any hybrid rejection so a short-circuit on dense failure below
82
+ // does not surface it as an unhandledRejection. We still `await` the
83
+ // original promise in the non-short-circuit path to observe its outcome.
84
+ hybridPromise.catch(() => {});
85
+
86
+ // Dense is the required signal — only paths with a dense cosine score are
87
+ // merged, so a dense rejection guarantees `[]` regardless of hybrid. Return
88
+ // immediately rather than blocking on hybrid latency.
89
+ let denseResults: QdrantSearchResult[];
90
+ try {
91
+ denseResults = await densePromise;
92
+ } catch (err) {
93
+ log.warn({ err }, "Dense PKB search failed; returning empty results");
94
+ return [];
95
+ }
96
+
97
+ let hybridResults: QdrantSearchResult[] = [];
98
+ if (useHybrid) {
99
+ try {
100
+ hybridResults = await hybridPromise;
101
+ } catch (err) {
102
+ log.warn(
103
+ { err },
104
+ "Hybrid PKB search failed; falling back to dense-only results",
105
+ );
106
+ }
107
+ }
84
108
 
85
109
  const denseByPath = collapseByPath(denseResults);
86
110
  const hybridByPath = useHybrid ? collapseByPath(hybridResults) : new Map();
87
111
 
88
- const allPaths = new Set<string>([
89
- ...denseByPath.keys(),
90
- ...hybridByPath.keys(),
91
- ]);
92
-
112
+ // Only surface paths that have a dense cosine score. Hybrid-only hits
113
+ // (paths that appeared in the hybrid query but fell outside the dense
114
+ // prefetch) would have `denseScore = 0`, which is guaranteed to fail any
115
+ // caller's cosine threshold — and because they'd sort ahead of dense
116
+ // matches (hybrid-first ranking), they'd evict dense-qualifying hits from
117
+ // the top-`limit` window before callers ever get a chance to gate on
118
+ // denseScore. Dropping them keeps gating meaningful with the pre-slice.
93
119
  const merged: PkbSearchResult[] = [];
94
- for (const path of allPaths) {
95
- const denseScore = denseByPath.get(path);
120
+ for (const [path, denseScore] of denseByPath) {
96
121
  const hybridScore = hybridByPath.get(path);
97
- // A path that only shows up in the hybrid query (past the dense prefetch
98
- // limit) has no cosine score to gate on. Carry denseScore = 0 so callers
99
- // see it but can filter it out with their threshold.
100
122
  merged.push({
101
123
  path,
102
- denseScore: denseScore ?? 0,
124
+ denseScore,
103
125
  ...(useHybrid && hybridScore !== undefined ? { hybridScore } : {}),
104
126
  });
105
127
  }
@@ -0,0 +1,60 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import { applyNestedDefaults } from "../config/loader.js";
4
+ import type { AssistantConfig } from "../config/types.js";
5
+ import { resolveQdrantUrl } from "./qdrant-client.js";
6
+
7
+ const DEFAULT_CONFIG: AssistantConfig = applyNestedDefaults({});
8
+
9
+ describe("resolveQdrantUrl", () => {
10
+ const savedPort = process.env.QDRANT_HTTP_PORT;
11
+ const savedUrl = process.env.QDRANT_URL;
12
+
13
+ beforeEach(() => {
14
+ delete process.env.QDRANT_HTTP_PORT;
15
+ delete process.env.QDRANT_URL;
16
+ });
17
+
18
+ afterEach(() => {
19
+ if (savedPort === undefined) delete process.env.QDRANT_HTTP_PORT;
20
+ else process.env.QDRANT_HTTP_PORT = savedPort;
21
+ if (savedUrl === undefined) delete process.env.QDRANT_URL;
22
+ else process.env.QDRANT_URL = savedUrl;
23
+ });
24
+
25
+ test("falls back to config when no env vars are set", () => {
26
+ expect(resolveQdrantUrl(DEFAULT_CONFIG)).toBe("http://127.0.0.1:6333");
27
+ });
28
+
29
+ test("honours QDRANT_URL when set", () => {
30
+ process.env.QDRANT_URL = "http://qdrant.example.com:6333";
31
+ expect(resolveQdrantUrl(DEFAULT_CONFIG)).toBe(
32
+ "http://qdrant.example.com:6333",
33
+ );
34
+ });
35
+
36
+ test("QDRANT_HTTP_PORT wins over QDRANT_URL", () => {
37
+ process.env.QDRANT_URL = "http://qdrant.example.com:6333";
38
+ process.env.QDRANT_HTTP_PORT = "20200";
39
+ expect(resolveQdrantUrl(DEFAULT_CONFIG)).toBe("http://127.0.0.1:20200");
40
+ });
41
+
42
+ test("QDRANT_HTTP_PORT wins over config default", () => {
43
+ process.env.QDRANT_HTTP_PORT = "20200";
44
+ expect(resolveQdrantUrl(DEFAULT_CONFIG)).toBe("http://127.0.0.1:20200");
45
+ });
46
+
47
+ test("respects a non-default config URL when no env is set", () => {
48
+ const config: AssistantConfig = {
49
+ ...DEFAULT_CONFIG,
50
+ memory: {
51
+ ...DEFAULT_CONFIG.memory,
52
+ qdrant: {
53
+ ...DEFAULT_CONFIG.memory.qdrant,
54
+ url: "http://custom-host:9999",
55
+ },
56
+ },
57
+ };
58
+ expect(resolveQdrantUrl(config)).toBe("http://custom-host:9999");
59
+ });
60
+ });
@@ -1,10 +1,35 @@
1
1
  import { QdrantClient as QdrantRestClient } from "@qdrant/js-client-rest";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
+ import { getQdrantHttpPortEnv, getQdrantUrlEnv } from "../config/env.js";
5
+ import type { AssistantConfig } from "../config/types.js";
4
6
  import { getLogger } from "../util/logger.js";
5
7
 
6
8
  const log = getLogger("qdrant-client");
7
9
 
10
+ /**
11
+ * Resolve the Qdrant base URL for this process.
12
+ *
13
+ * Precedence (highest first):
14
+ * 1. `QDRANT_HTTP_PORT` — a locally-spawned Qdrant sidecar on 127.0.0.1:<port>.
15
+ * Set by the CLI when spawning the daemon with a non-default port
16
+ * (multi-local instances).
17
+ * 2. `QDRANT_URL` — an external Qdrant instance (K8s sidecar, remote URL).
18
+ * 3. `config.memory.qdrant.url` — static config (defaults to
19
+ * `http://127.0.0.1:6333`).
20
+ *
21
+ * Every caller that constructs a Qdrant URL should route through this helper
22
+ * so the precedence stays consistent across the daemon, CLI, and any
23
+ * background jobs that re-read the config.
24
+ */
25
+ export function resolveQdrantUrl(config: AssistantConfig): string {
26
+ const port = getQdrantHttpPortEnv();
27
+ if (port) return `http://127.0.0.1:${port}`;
28
+ const url = getQdrantUrlEnv();
29
+ if (url) return url;
30
+ return config.memory.qdrant.url;
31
+ }
32
+
8
33
  export interface QdrantSparseVector {
9
34
  indices: number[];
10
35
  values: number[];
@@ -28,6 +28,7 @@ export const cronJobs = sqliteTable("cron_jobs", {
28
28
  reuseConversation: integer("reuse_conversation", { mode: "boolean" })
29
29
  .notNull()
30
30
  .default(false), // reuse the same conversation across runs
31
+ script: text("script"), // shell command for script mode (nullable, only used when mode = 'script')
31
32
  createdAt: integer("created_at").notNull(),
32
33
  updatedAt: integer("updated_at").notNull(),
33
34
  });
@@ -20,7 +20,7 @@ export const oauthProviders = sqliteTable("oauth_providers", {
20
20
  userinfoUrl: text("userinfo_url"),
21
21
  baseUrl: text("base_url"),
22
22
  defaultScopes: text("default_scopes").notNull().default("[]"),
23
- scopePolicy: text("scope_policy").notNull().default("{}"),
23
+ availableScopes: text("available_scopes"),
24
24
  scopeSeparator: text("scope_separator").notNull().default(" "),
25
25
  authorizeParams: text("extra_params"),
26
26
  pingUrl: text("ping_url"),
@@ -30,6 +30,9 @@ export const oauthProviders = sqliteTable("oauth_providers", {
30
30
  revokeUrl: text("revoke_url"),
31
31
  revokeBodyTemplate: text("revoke_body_template"),
32
32
  managedServiceConfigKey: text("managed_service_config_key"),
33
+ managedServiceIsPaid: integer("managed_service_is_paid", { mode: "boolean" })
34
+ .notNull()
35
+ .default(false),
33
36
  displayLabel: text("display_name"),
34
37
  description: text("description"),
35
38
  dashboardUrl: text("dashboard_url"),