@vellumai/assistant 0.3.4 → 0.3.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 (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -1,62 +1,91 @@
1
1
  import { and, desc, eq, inArray, notInArray } from 'drizzle-orm';
2
2
  import { getLogger } from '../../util/logger.js';
3
- import { getDb } from '../db.js';
3
+ import { getDb, rawAll } from '../db.js';
4
4
  import { memorySegments } from '../schema.js';
5
5
  import type { Candidate, CandidateType } from './types.js';
6
6
  import { computeRecencyScore } from './ranking.js';
7
7
 
8
8
  const log = getLogger('memory-retriever');
9
9
 
10
+ // Threshold beyond which a raw SQL query is considered slow and logged as a warning.
11
+ const SLOW_QUERY_MS = 2000;
12
+
13
+ /**
14
+ * Execute a synchronous raw SQL query with timing. Logs a warning if the
15
+ * query exceeds SLOW_QUERY_MS. Since bun:sqlite queries are synchronous,
16
+ * we can't abort them mid-execution, but we can detect and report slowness.
17
+ */
18
+ function timedRawQuery<T>(label: string, fn: () => T[]): T[] {
19
+ const start = performance.now();
20
+ const result = fn();
21
+ const elapsed = performance.now() - start;
22
+ if (elapsed >= SLOW_QUERY_MS) {
23
+ log.warn({ label, elapsedMs: Math.round(elapsed) }, 'Raw SQL query exceeded slow threshold');
24
+ }
25
+ return result;
26
+ }
27
+
28
+ interface FtsRow {
29
+ segment_id: string;
30
+ message_id: string;
31
+ text: string;
32
+ created_at: number;
33
+ rank: number;
34
+ }
35
+
10
36
  export function lexicalSearch(query: string, limit: number, excludedMessageIds: string[] = [], scopeIds?: string[]): Candidate[] {
11
37
  const trimmed = query.trim();
12
38
  if (trimmed.length === 0 || limit <= 0) return [];
13
39
  const matchQuery = buildFtsMatchQuery(trimmed);
14
40
  if (!matchQuery) return [];
15
41
  const excluded = new Set(excludedMessageIds);
16
- const db = getDb();
17
- const raw = (db as unknown as { $client: { query: (q: string) => { all: (...params: unknown[]) => unknown[] } } }).$client;
18
- let rows: Array<{
19
- segment_id: string;
20
- message_id: string;
21
- text: string;
22
- created_at: number;
23
- rank: number;
24
- }> = [];
25
- const queryLimit = excluded.size > 0
26
- ? Math.max(limit + 24, limit * 2)
27
- : limit;
28
42
  const scopeClause = scopeIds
29
43
  ? ` AND s.scope_id IN (${scopeIds.map(() => '?').join(',')})`
30
44
  : '';
31
- const params: unknown[] = [matchQuery, ...(scopeIds ?? []), queryLimit];
32
- try {
33
- rows = raw.query(`
34
- SELECT
35
- f.segment_id AS segment_id,
36
- s.message_id AS message_id,
37
- s.text AS text,
38
- s.created_at AS created_at,
39
- bm25(memory_segment_fts) AS rank
40
- FROM memory_segment_fts f
41
- JOIN memory_segments s ON s.id = f.segment_id
42
- WHERE memory_segment_fts MATCH ?${scopeClause}
43
- ORDER BY rank
44
- LIMIT ?
45
- `).all(...params) as Array<{
46
- segment_id: string;
47
- message_id: string;
48
- text: string;
49
- created_at: number;
50
- rank: number;
51
- }>;
52
- } catch (err) {
53
- log.warn({ err, query: truncate(trimmed, 80) }, 'Memory lexical search query parse failed');
54
- return [];
45
+
46
+ // Adaptive overfetch: when exclusions are present, double the SQL LIMIT until we
47
+ // collect `limit` visible rows or the DB has no more matching results to offer.
48
+ // This handles dense exclusion sets without wasteful fixed-ratio over-fetching.
49
+ const MAX_FETCH_MULTIPLIER = 8;
50
+ let queryLimit = excluded.size > 0 ? Math.max(limit * 2, limit + 24) : limit;
51
+ let visibleRows: FtsRow[] = [];
52
+
53
+ while (true) {
54
+ let rows: FtsRow[] = [];
55
+ try {
56
+ rows = timedRawQuery('lexicalSearch', () =>
57
+ rawAll<FtsRow>(`
58
+ SELECT
59
+ f.segment_id AS segment_id,
60
+ s.message_id AS message_id,
61
+ s.text AS text,
62
+ s.created_at AS created_at,
63
+ bm25(memory_segment_fts) AS rank
64
+ FROM memory_segment_fts f
65
+ JOIN memory_segments s ON s.id = f.segment_id
66
+ WHERE memory_segment_fts MATCH ?${scopeClause}
67
+ ORDER BY rank
68
+ LIMIT ?
69
+ `, matchQuery, ...(scopeIds ?? []), queryLimit),
70
+ );
71
+ } catch (err) {
72
+ log.warn({ err, query: truncate(trimmed, 80) }, 'Memory lexical search query parse failed');
73
+ return [];
74
+ }
75
+
76
+ visibleRows = excluded.size > 0
77
+ ? rows.filter((row) => !excluded.has(row.message_id))
78
+ : rows;
79
+
80
+ // Stop when we have enough rows, the DB returned fewer than requested
81
+ // (no more results exist), or we've reached the safety cap.
82
+ if (visibleRows.length >= limit || rows.length < queryLimit || queryLimit >= limit * MAX_FETCH_MULTIPLIER) {
83
+ break;
84
+ }
85
+ queryLimit = Math.min(queryLimit * 2, limit * MAX_FETCH_MULTIPLIER);
55
86
  }
56
87
 
57
- const visibleRows = excluded.size > 0
58
- ? rows.filter((row) => !excluded.has(row.message_id)).slice(0, limit)
59
- : rows;
88
+ visibleRows = visibleRows.slice(0, limit);
60
89
 
61
90
  const finiteRanks = visibleRows
62
91
  .map((row) => row.rank)
@@ -124,17 +153,19 @@ export function recencySearch(conversationId: string, limit: number, excludedMes
124
153
  * Supplements FTS-based lexical search with LIKE-based matching on items.
125
154
  */
126
155
  export function directItemSearch(query: string, limit: number, scopeIds?: string[]): Candidate[] {
127
- const db = getDb();
128
156
  const tokens = [...new Set(query
129
157
  .toLowerCase()
130
158
  .split(/[^a-z0-9_.-]+/g)
131
159
  .filter((t) => t.length >= 2))];
132
160
  if (tokens.length === 0) return [];
133
161
 
134
- const raw = (db as unknown as { $client: { query: (q: string) => { all: (...params: unknown[]) => unknown[] } } }).$client;
135
162
  const likeClauses = tokens.map(
136
- (t) => `(LOWER(subject) LIKE '%${escapeSqlLike(t)}%' OR LOWER(statement) LIKE '%${escapeSqlLike(t)}%')`,
163
+ () => `(LOWER(subject) LIKE ? OR LOWER(statement) LIKE ?)`,
137
164
  );
165
+ const likeParams = tokens.flatMap((t) => {
166
+ const pattern = `%${escapeLikeWildcards(t)}%`;
167
+ return [pattern, pattern];
168
+ });
138
169
  const scopeClause = scopeIds
139
170
  ? ` AND scope_id IN (${scopeIds.map(() => '?').join(',')})`
140
171
  : '';
@@ -145,9 +176,8 @@ export function directItemSearch(query: string, limit: number, scopeIds?: string
145
176
  ORDER BY last_seen_at DESC
146
177
  LIMIT ?
147
178
  `;
148
- const params: unknown[] = [...(scopeIds ?? []), limit];
149
179
 
150
- let rows: Array<{
180
+ interface ItemRow {
151
181
  id: string;
152
182
  kind: string;
153
183
  subject: string;
@@ -156,9 +186,13 @@ export function directItemSearch(query: string, limit: number, scopeIds?: string
156
186
  importance: number | null;
157
187
  first_seen_at: number;
158
188
  last_seen_at: number;
159
- }> = [];
189
+ }
190
+
191
+ let rows: ItemRow[] = [];
160
192
  try {
161
- rows = raw.query(sqlQuery).all(...params) as typeof rows;
193
+ rows = timedRawQuery('directItemSearch', () =>
194
+ rawAll<ItemRow>(sqlQuery, ...likeParams, ...(scopeIds ?? []), limit),
195
+ );
162
196
  } catch {
163
197
  return [];
164
198
  }
@@ -212,11 +246,6 @@ export function buildFtsMatchQuery(text: string): string | null {
212
246
  .join(' OR ');
213
247
  }
214
248
 
215
- export function escapeSqlLike(s: string): string {
216
- return s.replace(/'/g, "''").replace(/%/g, '').replace(/_/g, '');
217
- }
218
-
219
- /** Escape only LIKE wildcards (% and _) for use with parameterized queries where the driver handles quoting. */
220
249
  export function escapeLikeWildcards(s: string): string {
221
250
  return s.replace(/%/g, '').replace(/_/g, '');
222
251
  }
@@ -1,9 +1,8 @@
1
1
  import { inArray, sql } from 'drizzle-orm';
2
- import Anthropic from '@anthropic-ai/sdk';
3
2
  import type { AssistantConfig, MemoryRerankingConfig } from '../../config/types.js';
4
- import { getConfig } from '../../config/loader.js';
5
3
  import { estimateTextTokens } from '../../context/token-estimator.js';
6
4
  import { getLogger } from '../../util/logger.js';
5
+ import { getConfiguredProvider, extractText, userMessage } from '../../providers/provider-send-message.js';
7
6
  import { getDb } from '../db.js';
8
7
  import { memoryItems } from '../schema.js';
9
8
  import type { Candidate, CandidateSource, ItemMetadata } from './types.js';
@@ -53,6 +52,7 @@ export function mergeCandidates(
53
52
  entity: Candidate[] = [],
54
53
  freshnessConfig?: { enabled: boolean; maxAgeDays: Record<string, number>; staleDecay: number; reinforcementShieldDays: number },
55
54
  relationScoreMultiplier?: number,
55
+ candidateDepthMap?: Map<string, number>,
56
56
  ): Candidate[] {
57
57
  // Build effective weight map that reflects the actual scoring weight for
58
58
  // each source. For entity_relation the static SOURCE_WEIGHTS entry is 1.0
@@ -126,7 +126,11 @@ export function mergeCandidates(
126
126
  const lastUsedAt = meta?.lastUsedAt ?? null;
127
127
  const freshnessWeight = computeFreshnessWeight(row, accessCount, lastUsedAt, freshnessConfig);
128
128
 
129
- const sourceWeight = effectiveWeights[row.source] ?? 1.0;
129
+ let sourceWeight = effectiveWeights[row.source] ?? 1.0;
130
+ if (row.source === 'entity_relation' && candidateDepthMap && relationScoreMultiplier != null) {
131
+ const depth = candidateDepthMap.get(row.key) ?? 1;
132
+ sourceWeight = Math.pow(relationScoreMultiplier, depth);
133
+ }
130
134
  row.finalScore = rrfScore * (0.5 + 0.5 * effectiveImportance) * trustWeight * freshnessWeight * sourceWeight;
131
135
  }
132
136
 
@@ -291,10 +295,9 @@ export async function rerankWithLLM(
291
295
  candidates: Candidate[],
292
296
  rerankingConfig: MemoryRerankingConfig,
293
297
  ): Promise<Candidate[]> {
294
- const config = getConfig();
295
- const apiKey = config.apiKeys.anthropic ?? process.env.ANTHROPIC_API_KEY;
296
- if (!apiKey) {
297
- log.debug('No Anthropic API key available for LLM re-ranking, skipping');
298
+ const provider = getConfiguredProvider();
299
+ if (!provider) {
300
+ log.debug('Configured provider unavailable for LLM re-ranking, skipping');
298
301
  return candidates;
299
302
  }
300
303
 
@@ -304,26 +307,27 @@ export async function rerankWithLLM(
304
307
  text: truncate(c.text, 200),
305
308
  }));
306
309
 
307
- const client = new Anthropic({ apiKey });
308
- const response = await client.messages.create({
309
- model: rerankingConfig.model,
310
- max_tokens: 1024,
311
- system: 'You are a relevance scoring assistant. Given a query and a list of memory candidates, rate each candidate\'s relevance to the query on a scale of 0-10. Return ONLY a JSON array of objects with "index" (the candidate index) and "score" (0-10 integer). No explanation.',
312
- messages: [{
313
- role: 'user',
314
- content: `Query: ${truncate(query, 200)}\n\nCandidates:\n${candidateList.map((c) => `[${c.index}] ${c.text}`).join('\n')}`,
315
- }],
316
- });
310
+ const response = await provider.sendMessage(
311
+ [userMessage(`Query: ${truncate(query, 200)}\n\nCandidates:\n${candidateList.map((c) => `[${c.index}] ${c.text}`).join('\n')}`)],
312
+ undefined,
313
+ 'You are a relevance scoring assistant. Given a query and a list of memory candidates, rate each candidate\'s relevance to the query on a scale of 0-10. Return ONLY a JSON array of objects with "index" (the candidate index) and "score" (0-10 integer). No explanation.',
314
+ {
315
+ config: {
316
+ model: rerankingConfig.model,
317
+ max_tokens: 1024,
318
+ },
319
+ },
320
+ );
317
321
 
318
322
  // Extract text from the response
319
- const textBlock = response.content.find((block) => block.type === 'text');
320
- if (!textBlock || textBlock.type !== 'text') {
323
+ const responseText = extractText(response);
324
+ if (!responseText) {
321
325
  log.warn('LLM re-ranking returned no text block, skipping');
322
326
  return candidates;
323
327
  }
324
328
 
325
329
  // Parse the JSON array from the response
326
- const jsonMatch = textBlock.text.match(/\[[\s\S]*\]/);
330
+ const jsonMatch = responseText.match(/\[[\s\S]*\]/);
327
331
  if (!jsonMatch) {
328
332
  log.warn('LLM re-ranking response did not contain JSON array, skipping');
329
333
  return candidates;
@@ -354,10 +358,10 @@ export async function rerankWithLLM(
354
358
 
355
359
  reranked.sort((a, b) => {
356
360
  // Scored items come before unscored items
357
- if (a.llmScore !== null && b.llmScore === null) return -1;
358
- if (a.llmScore === null && b.llmScore !== null) return 1;
361
+ if (a.llmScore != null && b.llmScore == null) return -1;
362
+ if (a.llmScore == null && b.llmScore != null) return 1;
359
363
  // Both scored: sort by score descending
360
- if (a.llmScore !== null && b.llmScore !== null) {
364
+ if (a.llmScore != null && b.llmScore != null) {
361
365
  const scoreDelta = b.llmScore - a.llmScore;
362
366
  if (scoreDelta !== 0) return scoreDelta;
363
367
  }
@@ -1,6 +1,7 @@
1
- import { eq } from 'drizzle-orm';
1
+ import { inArray } from 'drizzle-orm';
2
2
  import { getDb } from '../db.js';
3
3
  import { getQdrantClient } from '../qdrant-client.js';
4
+ import type { QdrantSearchResult } from '../qdrant-client.js';
4
5
  import {
5
6
  memoryItems,
6
7
  memoryItemSources,
@@ -9,6 +10,84 @@ import {
9
10
  } from '../schema.js';
10
11
  import type { Candidate } from './types.js';
11
12
  import { computeRecencyScore } from './ranking.js';
13
+ import { getLogger } from '../../util/logger.js';
14
+
15
+ const log = getLogger('qdrant-circuit-breaker');
16
+
17
+ // ── Qdrant circuit breaker ───────────────────────────────────────────
18
+ // After FAILURE_THRESHOLD consecutive Qdrant failures, stop sending
19
+ // requests (open state). After COOLDOWN_MS, allow a single probe
20
+ // request (half-open). If the probe succeeds, close the circuit; if it
21
+ // fails, re-open and restart the cooldown.
22
+
23
+ const FAILURE_THRESHOLD = 5;
24
+ const COOLDOWN_MS = 60_000;
25
+
26
+ type BreakerState = 'closed' | 'open' | 'half-open';
27
+
28
+ let breakerState: BreakerState = 'closed';
29
+ let consecutiveFailures = 0;
30
+ let openedAt = 0;
31
+ // Ensures only one request passes through during half-open state
32
+ let halfOpenProbeInFlight = false;
33
+
34
+ function qdrantBreakerAllows(): boolean {
35
+ if (breakerState === 'closed') return true;
36
+ if (breakerState === 'open') {
37
+ if (Date.now() - openedAt >= COOLDOWN_MS) {
38
+ breakerState = 'half-open';
39
+ halfOpenProbeInFlight = true;
40
+ log.info('Qdrant circuit breaker entering half-open state — allowing probe request');
41
+ return true;
42
+ }
43
+ return false;
44
+ }
45
+ // half-open: only allow through if no probe is already in flight
46
+ if (halfOpenProbeInFlight) return false;
47
+ halfOpenProbeInFlight = true;
48
+ return true;
49
+ }
50
+
51
+ function qdrantBreakerRecordSuccess(): void {
52
+ if (breakerState !== 'closed') {
53
+ log.info({ previousFailures: consecutiveFailures }, 'Qdrant circuit breaker closed — search succeeded');
54
+ }
55
+ consecutiveFailures = 0;
56
+ breakerState = 'closed';
57
+ openedAt = 0;
58
+ halfOpenProbeInFlight = false;
59
+ }
60
+
61
+ function qdrantBreakerRecordFailure(): void {
62
+ consecutiveFailures++;
63
+ halfOpenProbeInFlight = false;
64
+ if (consecutiveFailures >= FAILURE_THRESHOLD) {
65
+ breakerState = 'open';
66
+ openedAt = Date.now();
67
+ log.warn(
68
+ { consecutiveFailures, cooldownMs: COOLDOWN_MS },
69
+ 'Qdrant circuit breaker opened — semantic search disabled until probe succeeds',
70
+ );
71
+ } else if (breakerState === 'half-open') {
72
+ // Probe failed — re-open
73
+ breakerState = 'open';
74
+ openedAt = Date.now();
75
+ log.warn('Qdrant circuit breaker re-opened — half-open probe failed');
76
+ }
77
+ }
78
+
79
+ /** @internal Test-only: reset circuit breaker state */
80
+ export function _resetQdrantBreaker(): void {
81
+ breakerState = 'closed';
82
+ consecutiveFailures = 0;
83
+ openedAt = 0;
84
+ halfOpenProbeInFlight = false;
85
+ }
86
+
87
+ /** @internal Test-only: get breaker state */
88
+ export function _getQdrantBreakerState(): { state: BreakerState; consecutiveFailures: number } {
89
+ return { state: breakerState, consecutiveFailures };
90
+ }
12
91
 
13
92
  export async function semanticSearch(
14
93
  queryVector: number[],
@@ -20,18 +99,79 @@ export async function semanticSearch(
20
99
  ): Promise<Candidate[]> {
21
100
  if (limit <= 0) return [];
22
101
 
102
+ // Circuit breaker: throw so the caller's .catch() marks the result as degraded
103
+ if (!qdrantBreakerAllows()) {
104
+ log.debug('Qdrant circuit breaker open — skipping semantic search');
105
+ throw new Error('Qdrant circuit breaker open');
106
+ }
107
+
23
108
  const qdrant = getQdrantClient();
24
109
 
25
110
  // Overfetch to account for items filtered out post-query (invalidated, excluded, etc.)
26
- const fetchLimit = limit * 2;
27
- const results = await qdrant.searchWithFilter(
28
- queryVector,
29
- fetchLimit,
30
- ['item', 'summary', 'segment'],
31
- excludedMessageIds,
32
- );
111
+ // Use 3x when exclusions are active to ensure enough results survive filtering
112
+ const overfetchMultiplier = excludedMessageIds.length > 0 ? 3 : 2;
113
+ const fetchLimit = limit * overfetchMultiplier;
114
+ let results: QdrantSearchResult[];
115
+ try {
116
+ results = await qdrant.searchWithFilter(
117
+ queryVector,
118
+ fetchLimit,
119
+ ['item', 'summary', 'segment'],
120
+ excludedMessageIds,
121
+ );
122
+ qdrantBreakerRecordSuccess();
123
+ } catch (err) {
124
+ qdrantBreakerRecordFailure();
125
+ throw err;
126
+ }
33
127
 
34
128
  const db = getDb();
129
+
130
+ // Batch-fetch all backing records upfront to avoid N+1 queries per result
131
+ const itemTargetIds: string[] = [];
132
+ const summaryTargetIds: string[] = [];
133
+ const segmentTargetIds: string[] = [];
134
+ for (const r of results) {
135
+ if (r.payload.target_type === 'item') itemTargetIds.push(r.payload.target_id);
136
+ else if (r.payload.target_type === 'summary') summaryTargetIds.push(r.payload.target_id);
137
+ else segmentTargetIds.push(r.payload.target_id);
138
+ }
139
+
140
+ const itemsMap = new Map<string, typeof memoryItems.$inferSelect>();
141
+ if (itemTargetIds.length > 0) {
142
+ const allItems = db.select().from(memoryItems).where(inArray(memoryItems.id, itemTargetIds)).all();
143
+ for (const item of allItems) itemsMap.set(item.id, item);
144
+ }
145
+
146
+ const sourcesMap = new Map<string, string[]>();
147
+ if (itemTargetIds.length > 0) {
148
+ const allSources = db.select({
149
+ memoryItemId: memoryItemSources.memoryItemId,
150
+ messageId: memoryItemSources.messageId,
151
+ }).from(memoryItemSources)
152
+ .where(inArray(memoryItemSources.memoryItemId, itemTargetIds))
153
+ .all();
154
+ for (const s of allSources) {
155
+ const existing = sourcesMap.get(s.memoryItemId);
156
+ if (existing) existing.push(s.messageId);
157
+ else sourcesMap.set(s.memoryItemId, [s.messageId]);
158
+ }
159
+ }
160
+
161
+ const summariesMap = new Map<string, typeof memorySummaries.$inferSelect>();
162
+ if (scopeIds && summaryTargetIds.length > 0) {
163
+ const allSummaries = db.select().from(memorySummaries).where(inArray(memorySummaries.id, summaryTargetIds)).all();
164
+ for (const s of allSummaries) summariesMap.set(s.id, s);
165
+ }
166
+
167
+ const segmentsMap = new Map<string, typeof memorySegments.$inferSelect>();
168
+ if (scopeIds && segmentTargetIds.length > 0) {
169
+ const allSegments = db.select().from(memorySegments).where(inArray(memorySegments.id, segmentTargetIds)).all();
170
+ for (const seg of allSegments) segmentsMap.set(seg.id, seg);
171
+ }
172
+
173
+ const excludedSet = excludedMessageIds.length > 0 ? new Set(excludedMessageIds) : null;
174
+
35
175
  const candidates: Candidate[] = [];
36
176
  for (const result of results) {
37
177
  const { payload, score } = result;
@@ -39,16 +179,14 @@ export async function semanticSearch(
39
179
  const createdAt = payload.created_at ?? Date.now();
40
180
 
41
181
  if (payload.target_type === 'item') {
42
- // Validate the backing memory item is still active and has non-excluded evidence
43
- const item = db.select().from(memoryItems).where(eq(memoryItems.id, payload.target_id)).get();
44
- if (!item || item.status !== 'active' || item.invalidAt !== null) continue;
182
+ const item = itemsMap.get(payload.target_id);
183
+ if (!item || item.status !== 'active' || item.invalidAt != null) continue;
45
184
  if (scopeIds && !scopeIds.includes(item.scopeId)) continue;
46
- const sources = db.select().from(memoryItemSources)
47
- .where(eq(memoryItemSources.memoryItemId, payload.target_id)).all();
48
- if (sources.length === 0) continue;
49
- if (excludedMessageIds.length > 0) {
50
- const nonExcluded = sources.filter((s) => !excludedMessageIds.includes(s.messageId));
51
- if (nonExcluded.length === 0) continue;
185
+ const sources = sourcesMap.get(payload.target_id);
186
+ if (!sources || sources.length === 0) continue;
187
+ if (excludedSet) {
188
+ const hasNonExcluded = sources.some((msgId) => !excludedSet.has(msgId));
189
+ if (!hasNonExcluded) continue;
52
190
  }
53
191
  candidates.push({
54
192
  key: `item:${payload.target_id}`,
@@ -67,7 +205,7 @@ export async function semanticSearch(
67
205
  });
68
206
  } else if (payload.target_type === 'summary') {
69
207
  if (scopeIds) {
70
- const summary = db.select().from(memorySummaries).where(eq(memorySummaries.id, payload.target_id)).get();
208
+ const summary = summariesMap.get(payload.target_id);
71
209
  if (!summary || !scopeIds.includes(summary.scopeId)) continue;
72
210
  }
73
211
  candidates.push({
@@ -87,7 +225,7 @@ export async function semanticSearch(
87
225
  });
88
226
  } else {
89
227
  if (scopeIds) {
90
- const segment = db.select().from(memorySegments).where(eq(memorySegments.id, payload.target_id)).get();
228
+ const segment = segmentsMap.get(payload.target_id);
91
229
  if (!segment || !scopeIds.includes(segment.scopeId)) continue;
92
230
  }
93
231
  candidates.push({
@@ -105,6 +105,7 @@ export interface EntitySearchResult {
105
105
  relationTraversedEdgeCount: number;
106
106
  relationNeighborEntityCount: number;
107
107
  relationExpandedItemCount: number;
108
+ candidateDepths?: Map<string, number>; // candidate key → BFS hop depth (1-based)
108
109
  }
109
110
 
110
111
  export interface MatchedEntityRow {
@@ -137,3 +138,26 @@ export interface MemorySearchResult {
137
138
  recency: number;
138
139
  };
139
140
  }
141
+
142
+ import type { EntityRelationType, EntityType } from '../entity-extractor.js';
143
+
144
+ export interface TraversalOptions {
145
+ maxEdges: number;
146
+ maxNeighborEntities: number;
147
+ maxDepth?: number; // default 3
148
+ relationTypes?: EntityRelationType[];
149
+ entityTypes?: EntityType[];
150
+ /** When true, only follow source→target edges (frontier must be on source side). */
151
+ directed?: boolean;
152
+ }
153
+
154
+ export interface TraversalResult {
155
+ neighborEntityIds: string[];
156
+ traversedEdgeCount: number;
157
+ neighborDepths: Map<string, number>; // entityId → depth (1-based)
158
+ }
159
+
160
+ export interface TraversalStep {
161
+ relationTypes?: EntityRelationType[];
162
+ entityTypes?: EntityType[];
163
+ }
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { eq, lte, or, and, isNull } from 'drizzle-orm';
8
8
  import { randomUUID, randomBytes } from 'node:crypto';
9
- import { getDb } from './db.js';
9
+ import { getDb, rawRun } from './db.js';
10
10
  import { sharedAppLinks } from './schema.js';
11
11
  import type { AppManifest } from '../bundler/manifest.js';
12
12
 
@@ -130,9 +130,8 @@ export function deleteSharedAppLinkByToken(shareToken: string): boolean {
130
130
  }
131
131
 
132
132
  export function incrementDownloadCount(shareToken: string): void {
133
- const db = getDb();
134
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
135
- raw.prepare(
133
+ rawRun(
136
134
  `UPDATE shared_app_links SET download_count = download_count + 1 WHERE share_token = ?`,
137
- ).run(shareToken);
135
+ shareToken,
136
+ );
138
137
  }
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Unit interval [0, 1] — used for confidence and importance fields on memory items.
5
+ * Coerces out-of-range numbers to the nearest bound rather than rejecting,
6
+ * since LLM-generated values occasionally exceed the range.
7
+ */
8
+ export const unitInterval = z.number().transform((v) => Math.min(1, Math.max(0, v)));
9
+
10
+ /** Zod schema for validating confidence/importance values on memory items. */
11
+ export const memoryItemScores = z.object({
12
+ confidence: unitInterval,
13
+ importance: unitInterval,
14
+ });
15
+
16
+ /** Clamp a numeric value to [0, 1]. */
17
+ export function clampUnitInterval(value: number): number {
18
+ return Math.min(1, Math.max(0, value));
19
+ }
@@ -5,8 +5,9 @@
5
5
  * Works across all platforms — Slack, Gmail, Discord, etc.
6
6
  */
7
7
 
8
- import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, readdirSync } from 'node:fs';
8
+ import { readFileSync, writeFileSync, unlinkSync, readdirSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
+ import { ensureDir, pathExists } from '../util/fs.js';
10
11
  import { randomUUID } from 'node:crypto';
11
12
  import { getRootDir } from '../util/platform.js';
12
13
 
@@ -24,9 +25,7 @@ export interface Draft {
24
25
 
25
26
  function getDraftsDir(platform: string): string {
26
27
  const dir = join(getRootDir(), 'workspace', 'data', 'drafts', platform);
27
- if (!existsSync(dir)) {
28
- mkdirSync(dir, { recursive: true });
29
- }
28
+ ensureDir(dir);
30
29
  return dir;
31
30
  }
32
31
 
@@ -61,7 +60,7 @@ export function createDraft(opts: {
61
60
 
62
61
  export function getDraft(platform: string, id: string): Draft | null {
63
62
  const path = getDraftPath(platform, id);
64
- if (!existsSync(path)) return null;
63
+ if (!pathExists(path)) return null;
65
64
  return JSON.parse(readFileSync(path, 'utf-8')) as Draft;
66
65
  }
67
66
 
@@ -82,7 +81,7 @@ export function listDrafts(platform: string): Draft[] {
82
81
 
83
82
  export function deleteDraft(platform: string, id: string): boolean {
84
83
  const path = getDraftPath(platform, id);
85
- if (!existsSync(path)) return false;
84
+ if (!pathExists(path)) return false;
86
85
  unlinkSync(path);
87
86
  return true;
88
87
  }
@@ -77,4 +77,6 @@ export interface SendOptions {
77
77
  subject?: string;
78
78
  /** For email: in-reply-to message ID */
79
79
  inReplyTo?: string;
80
+ /** Optional assistant scope for multi-assistant channels. */
81
+ assistantId?: string;
80
82
  }