@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,4 +1,4 @@
1
- import { sqliteTable, text, integer, real, blob } from 'drizzle-orm/sqlite-core';
1
+ import { sqliteTable, text, integer, real, blob, index } from 'drizzle-orm/sqlite-core';
2
2
 
3
3
  export const conversations = sqliteTable('conversations', {
4
4
  id: text('id').primaryKey(),
@@ -12,7 +12,9 @@ export const conversations = sqliteTable('conversations', {
12
12
  contextCompactedMessageCount: integer('context_compacted_message_count').notNull().default(0),
13
13
  contextCompactedAt: integer('context_compacted_at'),
14
14
  threadType: text('thread_type').notNull().default('standard'),
15
+ source: text('source').notNull().default('user'),
15
16
  memoryScopeId: text('memory_scope_id').notNull().default('default'),
17
+ originChannel: text('origin_channel'),
16
18
  });
17
19
 
18
20
  export const messages = sqliteTable('messages', {
@@ -24,7 +26,9 @@ export const messages = sqliteTable('messages', {
24
26
  content: text('content').notNull(),
25
27
  createdAt: integer('created_at').notNull(),
26
28
  metadata: text('metadata'),
27
- });
29
+ }, (table) => [
30
+ index('idx_messages_conversation_id').on(table.conversationId),
31
+ ]);
28
32
 
29
33
  export const toolInvocations = sqliteTable('tool_invocations', {
30
34
  id: text('id').primaryKey(),
@@ -38,7 +42,9 @@ export const toolInvocations = sqliteTable('tool_invocations', {
38
42
  riskLevel: text('risk_level').notNull(),
39
43
  durationMs: integer('duration_ms').notNull(),
40
44
  createdAt: integer('created_at').notNull(),
41
- });
45
+ }, (table) => [
46
+ index('idx_tool_invocations_conversation_id').on(table.conversationId),
47
+ ]);
42
48
 
43
49
  export const memorySegments = sqliteTable('memory_segments', {
44
50
  id: text('id').primaryKey(),
@@ -56,7 +62,10 @@ export const memorySegments = sqliteTable('memory_segments', {
56
62
  contentHash: text('content_hash'),
57
63
  createdAt: integer('created_at').notNull(),
58
64
  updatedAt: integer('updated_at').notNull(),
59
- });
65
+ }, (table) => [
66
+ index('idx_memory_segments_scope_id').on(table.scopeId),
67
+ index('idx_memory_segments_conversation_id').on(table.conversationId),
68
+ ]);
60
69
 
61
70
  export const memoryItems = sqliteTable('memory_items', {
62
71
  id: text('id').primaryKey(),
@@ -75,7 +84,10 @@ export const memoryItems = sqliteTable('memory_items', {
75
84
  lastUsedAt: integer('last_used_at'),
76
85
  validFrom: integer('valid_from'),
77
86
  invalidAt: integer('invalid_at'),
78
- });
87
+ }, (table) => [
88
+ index('idx_memory_items_scope_id').on(table.scopeId),
89
+ index('idx_memory_items_fingerprint').on(table.fingerprint),
90
+ ]);
79
91
 
80
92
  export const memoryItemSources = sqliteTable('memory_item_sources', {
81
93
  memoryItemId: text('memory_item_id')
@@ -105,7 +117,9 @@ export const memoryItemConflicts = sqliteTable('memory_item_conflicts', {
105
117
  resolvedAt: integer('resolved_at'),
106
118
  createdAt: integer('created_at').notNull(),
107
119
  updatedAt: integer('updated_at').notNull(),
108
- });
120
+ }, (table) => [
121
+ index('idx_memory_item_conflicts_scope_id').on(table.scopeId),
122
+ ]);
109
123
 
110
124
  export const memorySummaries = sqliteTable('memory_summaries', {
111
125
  id: text('id').primaryKey(),
@@ -119,7 +133,9 @@ export const memorySummaries = sqliteTable('memory_summaries', {
119
133
  endAt: integer('end_at').notNull(),
120
134
  createdAt: integer('created_at').notNull(),
121
135
  updatedAt: integer('updated_at').notNull(),
122
- });
136
+ }, (table) => [
137
+ index('idx_memory_summaries_scope_id').on(table.scopeId),
138
+ ]);
123
139
 
124
140
  export const memoryEmbeddings = sqliteTable('memory_embeddings', {
125
141
  id: text('id').primaryKey(),
@@ -551,6 +567,8 @@ export const callSessions = sqliteTable('call_sessions', {
551
567
  status: text('status').notNull().default('initiated'),
552
568
  callerIdentityMode: text('caller_identity_mode'),
553
569
  callerIdentitySource: text('caller_identity_source'),
570
+ assistantId: text('assistant_id'),
571
+ initiatedFromConversationId: text('initiated_from_conversation_id'),
554
572
  startedAt: integer('started_at'),
555
573
  endedAt: integer('ended_at'),
556
574
  lastError: text('last_error'),
@@ -680,3 +698,206 @@ export const channelGuardianRateLimits = sqliteTable('channel_guardian_rate_limi
680
698
  createdAt: integer('created_at').notNull(),
681
699
  updatedAt: integer('updated_at').notNull(),
682
700
  });
701
+
702
+ // ── Media Assets ─────────────────────────────────────────────────────
703
+
704
+ export const mediaAssets = sqliteTable('media_assets', {
705
+ id: text('id').primaryKey(),
706
+ title: text('title').notNull(),
707
+ filePath: text('file_path').notNull(),
708
+ mimeType: text('mime_type').notNull(),
709
+ durationSeconds: real('duration_seconds'),
710
+ fileHash: text('file_hash').notNull(),
711
+ status: text('status').notNull().default('registered'), // registered | processing | indexed | failed
712
+ mediaType: text('media_type').notNull(), // video | audio | image
713
+ metadata: text('metadata'), // JSON
714
+ createdAt: integer('created_at').notNull(),
715
+ updatedAt: integer('updated_at').notNull(),
716
+ });
717
+
718
+ export const processingStages = sqliteTable('processing_stages', {
719
+ id: text('id').primaryKey(),
720
+ assetId: text('asset_id').notNull()
721
+ .references(() => mediaAssets.id, { onDelete: 'cascade' }),
722
+ stage: text('stage').notNull(),
723
+ status: text('status').notNull().default('pending'), // pending | running | completed | failed
724
+ progress: integer('progress').notNull().default(0), // 0-100
725
+ lastError: text('last_error'),
726
+ startedAt: integer('started_at'),
727
+ completedAt: integer('completed_at'),
728
+ });
729
+
730
+ export const mediaKeyframes = sqliteTable('media_keyframes', {
731
+ id: text('id').primaryKey(),
732
+ assetId: text('asset_id').notNull()
733
+ .references(() => mediaAssets.id, { onDelete: 'cascade' }),
734
+ timestamp: real('timestamp').notNull(),
735
+ filePath: text('file_path').notNull(),
736
+ metadata: text('metadata'), // JSON
737
+ createdAt: integer('created_at').notNull(),
738
+ });
739
+
740
+ export const mediaVisionOutputs = sqliteTable('media_vision_outputs', {
741
+ id: text('id').primaryKey(),
742
+ assetId: text('asset_id').notNull()
743
+ .references(() => mediaAssets.id, { onDelete: 'cascade' }),
744
+ keyframeId: text('keyframe_id').notNull()
745
+ .references(() => mediaKeyframes.id, { onDelete: 'cascade' }),
746
+ analysisType: text('analysis_type').notNull(),
747
+ output: text('output').notNull(), // JSON
748
+ confidence: real('confidence'),
749
+ createdAt: integer('created_at').notNull(),
750
+ });
751
+
752
+ export const mediaTimelines = sqliteTable('media_timelines', {
753
+ id: text('id').primaryKey(),
754
+ assetId: text('asset_id').notNull()
755
+ .references(() => mediaAssets.id, { onDelete: 'cascade' }),
756
+ startTime: real('start_time').notNull(),
757
+ endTime: real('end_time').notNull(),
758
+ segmentType: text('segment_type').notNull(),
759
+ attributes: text('attributes'), // JSON
760
+ confidence: real('confidence'),
761
+ createdAt: integer('created_at').notNull(),
762
+ });
763
+
764
+ export const mediaEvents = sqliteTable('media_events', {
765
+ id: text('id').primaryKey(),
766
+ assetId: text('asset_id').notNull()
767
+ .references(() => mediaAssets.id, { onDelete: 'cascade' }),
768
+ eventType: text('event_type').notNull(),
769
+ startTime: real('start_time').notNull(),
770
+ endTime: real('end_time').notNull(),
771
+ confidence: real('confidence').notNull(),
772
+ reasons: text('reasons').notNull(), // JSON array
773
+ metadata: text('metadata'), // JSON
774
+ createdAt: integer('created_at').notNull(),
775
+ });
776
+
777
+ export const mediaTrackingProfiles = sqliteTable('media_tracking_profiles', {
778
+ id: text('id').primaryKey(),
779
+ assetId: text('asset_id').notNull()
780
+ .references(() => mediaAssets.id, { onDelete: 'cascade' }),
781
+ capabilities: text('capabilities').notNull(), // JSON: { [capName]: { enabled, tier } }
782
+ createdAt: integer('created_at').notNull(),
783
+ });
784
+
785
+ export const mediaEventFeedback = sqliteTable('media_event_feedback', {
786
+ id: text('id').primaryKey(),
787
+ assetId: text('asset_id').notNull()
788
+ .references(() => mediaAssets.id, { onDelete: 'cascade' }),
789
+ eventId: text('event_id').notNull()
790
+ .references(() => mediaEvents.id, { onDelete: 'cascade' }),
791
+ feedbackType: text('feedback_type').notNull(), // correct | incorrect | boundary_edit | missed
792
+ originalStartTime: real('original_start_time'),
793
+ originalEndTime: real('original_end_time'),
794
+ correctedStartTime: real('corrected_start_time'),
795
+ correctedEndTime: real('corrected_end_time'),
796
+ notes: text('notes'),
797
+ createdAt: integer('created_at').notNull(),
798
+ });
799
+
800
+ // ── Guardian Action Requests (cross-channel voice guardian) ──────────
801
+
802
+ export const guardianActionRequests = sqliteTable('guardian_action_requests', {
803
+ id: text('id').primaryKey(),
804
+ assistantId: text('assistant_id').notNull().default('self'),
805
+ kind: text('kind').notNull(), // 'ask_guardian'
806
+ sourceChannel: text('source_channel').notNull(), // 'voice'
807
+ sourceConversationId: text('source_conversation_id').notNull(),
808
+ callSessionId: text('call_session_id')
809
+ .notNull()
810
+ .references(() => callSessions.id, { onDelete: 'cascade' }),
811
+ pendingQuestionId: text('pending_question_id')
812
+ .notNull()
813
+ .references(() => callPendingQuestions.id, { onDelete: 'cascade' }),
814
+ questionText: text('question_text').notNull(),
815
+ requestCode: text('request_code').notNull(), // short human-readable code for routing replies
816
+ status: text('status').notNull().default('pending'), // pending | answered | expired | cancelled
817
+ answerText: text('answer_text'),
818
+ answeredByChannel: text('answered_by_channel'),
819
+ answeredByExternalUserId: text('answered_by_external_user_id'),
820
+ answeredAt: integer('answered_at'),
821
+ expiresAt: integer('expires_at').notNull(),
822
+ createdAt: integer('created_at').notNull(),
823
+ updatedAt: integer('updated_at').notNull(),
824
+ });
825
+
826
+ // ── Guardian Action Deliveries (per-channel delivery tracking) ───────
827
+
828
+ export const guardianActionDeliveries = sqliteTable('guardian_action_deliveries', {
829
+ id: text('id').primaryKey(),
830
+ requestId: text('request_id')
831
+ .notNull()
832
+ .references(() => guardianActionRequests.id, { onDelete: 'cascade' }),
833
+ destinationChannel: text('destination_channel').notNull(), // 'telegram' | 'sms' | 'macos'
834
+ destinationConversationId: text('destination_conversation_id'),
835
+ destinationChatId: text('destination_chat_id'),
836
+ destinationExternalUserId: text('destination_external_user_id'),
837
+ status: text('status').notNull().default('pending'), // pending | sent | failed | answered | expired | cancelled
838
+ sentAt: integer('sent_at'),
839
+ respondedAt: integer('responded_at'),
840
+ lastError: text('last_error'),
841
+ createdAt: integer('created_at').notNull(),
842
+ updatedAt: integer('updated_at').notNull(),
843
+ });
844
+
845
+ // ── Assistant Inbox ──────────────────────────────────────────────────
846
+
847
+ export const assistantIngressInvites = sqliteTable('assistant_ingress_invites', {
848
+ id: text('id').primaryKey(),
849
+ assistantId: text('assistant_id').notNull().default('self'),
850
+ sourceChannel: text('source_channel').notNull(),
851
+ tokenHash: text('token_hash').notNull(),
852
+ createdBySessionId: text('created_by_session_id'),
853
+ note: text('note'),
854
+ maxUses: integer('max_uses').notNull().default(1),
855
+ useCount: integer('use_count').notNull().default(0),
856
+ expiresAt: integer('expires_at').notNull(),
857
+ status: text('status').notNull().default('active'),
858
+ redeemedByExternalUserId: text('redeemed_by_external_user_id'),
859
+ redeemedByExternalChatId: text('redeemed_by_external_chat_id'),
860
+ redeemedAt: integer('redeemed_at'),
861
+ createdAt: integer('created_at').notNull(),
862
+ updatedAt: integer('updated_at').notNull(),
863
+ });
864
+
865
+ export const assistantIngressMembers = sqliteTable('assistant_ingress_members', {
866
+ id: text('id').primaryKey(),
867
+ assistantId: text('assistant_id').notNull().default('self'),
868
+ sourceChannel: text('source_channel').notNull(),
869
+ externalUserId: text('external_user_id'),
870
+ externalChatId: text('external_chat_id'),
871
+ displayName: text('display_name'),
872
+ username: text('username'),
873
+ status: text('status').notNull().default('pending'),
874
+ policy: text('policy').notNull().default('allow'),
875
+ inviteId: text('invite_id')
876
+ .references(() => assistantIngressInvites.id),
877
+ createdBySessionId: text('created_by_session_id'),
878
+ revokedReason: text('revoked_reason'),
879
+ blockedReason: text('blocked_reason'),
880
+ lastSeenAt: integer('last_seen_at'),
881
+ createdAt: integer('created_at').notNull(),
882
+ updatedAt: integer('updated_at').notNull(),
883
+ });
884
+
885
+ export const assistantInboxThreadState = sqliteTable('assistant_inbox_thread_state', {
886
+ conversationId: text('conversation_id')
887
+ .primaryKey()
888
+ .references(() => conversations.id, { onDelete: 'cascade' }),
889
+ assistantId: text('assistant_id').notNull().default('self'),
890
+ sourceChannel: text('source_channel').notNull(),
891
+ externalChatId: text('external_chat_id').notNull(),
892
+ externalUserId: text('external_user_id'),
893
+ displayName: text('display_name'),
894
+ username: text('username'),
895
+ lastInboundAt: integer('last_inbound_at'),
896
+ lastOutboundAt: integer('last_outbound_at'),
897
+ lastMessageAt: integer('last_message_at'),
898
+ unreadCount: integer('unread_count').notNull().default(0),
899
+ pendingEscalationCount: integer('pending_escalation_count').notNull().default(0),
900
+ hasPendingEscalation: integer('has_pending_escalation').notNull().default(0),
901
+ createdAt: integer('created_at').notNull(),
902
+ updatedAt: integer('updated_at').notNull(),
903
+ });
@@ -1,14 +1,14 @@
1
1
  import { and, desc, eq, inArray, isNull, or } from 'drizzle-orm';
2
2
  import type { MemoryEntityConfig } from '../../config/types.js';
3
3
  import { getLogger } from '../../util/logger.js';
4
- import { getDb } from '../db.js';
4
+ import { getDb, rawAll } from '../db.js';
5
5
  import {
6
6
  memoryEntityRelations,
7
7
  memoryItemEntities,
8
8
  memoryItems,
9
9
  memoryItemSources,
10
10
  } from '../schema.js';
11
- import type { Candidate, CandidateSource, CandidateType, EntitySearchResult, MatchedEntityRow } from './types.js';
11
+ import type { Candidate, CandidateSource, CandidateType, EntitySearchResult, MatchedEntityRow, TraversalOptions, TraversalResult, TraversalStep } from './types.js';
12
12
  import { computeRecencyScore } from './ranking.js';
13
13
 
14
14
  const log = getLogger('memory-retriever');
@@ -51,15 +51,16 @@ export function entitySearch(
51
51
  }
52
52
 
53
53
  const relationSeedEntityCount = seedEntityIds.length;
54
+
54
55
  const {
55
56
  neighborEntityIds,
56
57
  traversedEdgeCount: relationTraversedEdgeCount,
57
- } = findNeighborEntities(
58
- seedEntityIds,
59
- relationConfig.maxEdges,
60
- relationConfig.maxNeighborEntities,
61
- relationConfig.maxDepth,
62
- );
58
+ neighborDepths,
59
+ } = findNeighborEntities(seedEntityIds, {
60
+ maxEdges: relationConfig.maxEdges,
61
+ maxNeighborEntities: relationConfig.maxNeighborEntities,
62
+ maxDepth: relationConfig.maxDepth,
63
+ });
63
64
  const relationNeighborEntityCount = neighborEntityIds.length;
64
65
  const directItemIds = new Set(directCandidates.map((candidate) => candidate.id));
65
66
  const relationCandidates = getEntityLinkedItemCandidates(neighborEntityIds, {
@@ -70,12 +71,47 @@ export function entitySearch(
70
71
  });
71
72
  const relationExpandedItemCount = relationCandidates.length;
72
73
 
74
+ // Build candidate key → BFS depth map so ranking can apply distance-based decay
75
+ const candidateDepths = new Map<string, number>();
76
+ if (relationCandidates.length > 0 && neighborDepths.size > 0) {
77
+ const db = getDb();
78
+ const itemIds = relationCandidates.map((c) => c.id);
79
+ const links = db
80
+ .select({
81
+ memoryItemId: memoryItemEntities.memoryItemId,
82
+ entityId: memoryItemEntities.entityId,
83
+ })
84
+ .from(memoryItemEntities)
85
+ .where(inArray(memoryItemEntities.memoryItemId, itemIds))
86
+ .all();
87
+
88
+ // For each item, find the minimum depth among its linked neighbor entities
89
+ const itemDepthMap = new Map<string, number>();
90
+ for (const link of links) {
91
+ const depth = neighborDepths.get(link.entityId);
92
+ if (depth !== undefined) {
93
+ const existing = itemDepthMap.get(link.memoryItemId);
94
+ if (existing === undefined || depth < existing) {
95
+ itemDepthMap.set(link.memoryItemId, depth);
96
+ }
97
+ }
98
+ }
99
+
100
+ for (const candidate of relationCandidates) {
101
+ const depth = itemDepthMap.get(candidate.id);
102
+ if (depth !== undefined) {
103
+ candidateDepths.set(candidate.key, depth);
104
+ }
105
+ }
106
+ }
107
+
73
108
  return {
74
109
  candidates: [...directCandidates, ...relationCandidates],
75
110
  relationSeedEntityCount,
76
111
  relationTraversedEdgeCount,
77
112
  relationNeighborEntityCount,
78
113
  relationExpandedItemCount,
114
+ candidateDepths,
79
115
  };
80
116
  }
81
117
 
@@ -86,6 +122,7 @@ export function emptyEntitySearchResult(): EntitySearchResult {
86
122
  relationTraversedEdgeCount: 0,
87
123
  relationNeighborEntityCount: 0,
88
124
  relationExpandedItemCount: 0,
125
+ candidateDepths: new Map(),
89
126
  };
90
127
  }
91
128
 
@@ -93,8 +130,6 @@ export function findMatchedEntities(query: string, maxMatches: number): MatchedE
93
130
  const trimmed = query.trim();
94
131
  if (trimmed.length === 0) return [];
95
132
 
96
- const db = getDb();
97
- const raw = (db as unknown as { $client: { query: (q: string) => { all: (...params: unknown[]) => unknown[] } } }).$client;
98
133
  const safeLimit = Math.max(1, Math.floor(maxMatches));
99
134
 
100
135
  // Tokenize query into words for entity matching (min length 3 to reduce false positives)
@@ -136,14 +171,12 @@ export function findMatchedEntities(query: string, maxMatches: number): MatchedE
136
171
  queryParams = [fullQuery, fullQuery];
137
172
  }
138
173
 
139
- let matchedEntities: MatchedEntityRow[] = [];
140
174
  try {
141
- matchedEntities = raw.query(entityQuery).all(...queryParams) as MatchedEntityRow[];
175
+ return rawAll<MatchedEntityRow>(entityQuery, ...queryParams);
142
176
  } catch (err) {
143
177
  log.warn({ err }, 'Entity search query failed');
144
178
  return [];
145
179
  }
146
- return matchedEntities;
147
180
  }
148
181
 
149
182
  /**
@@ -152,18 +185,19 @@ export function findMatchedEntities(query: string, maxMatches: number): MatchedE
152
185
  */
153
186
  export function findNeighborEntities(
154
187
  seedEntityIds: string[],
155
- maxEdges: number,
156
- maxNeighborEntities: number,
157
- maxDepth: number = 3,
158
- ): { neighborEntityIds: string[]; traversedEdgeCount: number } {
188
+ opts: TraversalOptions,
189
+ ): TraversalResult {
190
+ const { maxEdges, maxNeighborEntities, maxDepth = 3, relationTypes, entityTypes, directed } = opts;
159
191
  if (seedEntityIds.length === 0 || maxEdges <= 0 || maxNeighborEntities <= 0 || maxDepth <= 0) {
160
- return { neighborEntityIds: [], traversedEdgeCount: 0 };
192
+ return { neighborEntityIds: [], traversedEdgeCount: 0, neighborDepths: new Map() };
161
193
  }
162
194
 
163
195
  const db = getDb();
164
196
  const visited = new Set<string>(seedEntityIds);
165
197
  const neighbors: string[] = [];
198
+ const neighborDepths = new Map<string, number>();
166
199
  let totalEdgesTraversed = 0;
200
+ const filterByEntityType = entityTypes && entityTypes.length > 0;
167
201
 
168
202
  // BFS frontier starts with seed entities
169
203
  let frontier = [...seedEntityIds];
@@ -174,19 +208,86 @@ export function findNeighborEntities(
174
208
  const edgeBudget = maxEdges - totalEdgesTraversed;
175
209
  if (edgeBudget <= 0) break;
176
210
 
177
- const rows = db
178
- .select({
179
- sourceEntityId: memoryEntityRelations.sourceEntityId,
180
- targetEntityId: memoryEntityRelations.targetEntityId,
181
- })
182
- .from(memoryEntityRelations)
183
- .where(or(
184
- inArray(memoryEntityRelations.sourceEntityId, frontier),
185
- inArray(memoryEntityRelations.targetEntityId, frontier),
186
- ))
187
- .orderBy(desc(memoryEntityRelations.lastSeenAt))
188
- .limit(Math.max(1, edgeBudget))
189
- .all();
211
+ let rows: Array<{ sourceEntityId: string; targetEntityId: string }>;
212
+
213
+ if (filterByEntityType) {
214
+ // When filtering by entity type, JOIN with memoryEntities on the neighbor
215
+ // side so non-matching edges are excluded at the SQL level and don't
216
+ // consume the edge budget.
217
+ const relationTypeCondition = relationTypes && relationTypes.length > 0
218
+ ? `AND r.relation IN (${relationTypes.map(() => '?').join(',')})`
219
+ : '';
220
+ const entityTypeFilter = `AND me.type IN (${entityTypes.map(() => '?').join(',')})`;
221
+ const frontierPlaceholders = frontier.map(() => '?').join(',');
222
+ const limit = Math.max(1, edgeBudget);
223
+
224
+ const relationParams = relationTypes && relationTypes.length > 0 ? relationTypes : [];
225
+
226
+ type EdgeRow = { sourceEntityId: string; targetEntityId: string };
227
+
228
+ if (directed) {
229
+ // GROUP BY deduplicates entity pairs that have multiple relation rows
230
+ const q1 = `
231
+ SELECT r.source_entity_id AS sourceEntityId, r.target_entity_id AS targetEntityId
232
+ FROM memory_entity_relations r
233
+ INNER JOIN memory_entities me ON me.id = r.target_entity_id
234
+ WHERE r.source_entity_id IN (${frontierPlaceholders})
235
+ ${relationTypeCondition} ${entityTypeFilter}
236
+ GROUP BY r.source_entity_id, r.target_entity_id
237
+ ORDER BY MAX(r.last_seen_at) DESC
238
+ LIMIT ?
239
+ `;
240
+ rows = rawAll<EdgeRow>(q1, ...frontier, ...relationParams, ...entityTypes, limit);
241
+ } else {
242
+ // Combine both directions in a single query with global recency
243
+ // ordering so the edge budget isn't direction-biased.
244
+ const q = `
245
+ SELECT sourceEntityId, targetEntityId FROM (
246
+ SELECT r.source_entity_id AS sourceEntityId, r.target_entity_id AS targetEntityId, r.last_seen_at
247
+ FROM memory_entity_relations r
248
+ INNER JOIN memory_entities me ON me.id = r.target_entity_id
249
+ WHERE r.source_entity_id IN (${frontierPlaceholders})
250
+ ${relationTypeCondition} ${entityTypeFilter}
251
+ UNION ALL
252
+ SELECT r.source_entity_id AS sourceEntityId, r.target_entity_id AS targetEntityId, r.last_seen_at
253
+ FROM memory_entity_relations r
254
+ INNER JOIN memory_entities me ON me.id = r.source_entity_id
255
+ WHERE r.target_entity_id IN (${frontierPlaceholders})
256
+ ${relationTypeCondition} ${entityTypeFilter}
257
+ )
258
+ GROUP BY sourceEntityId, targetEntityId
259
+ ORDER BY MAX(last_seen_at) DESC
260
+ LIMIT ?
261
+ `;
262
+ rows = rawAll<EdgeRow>(
263
+ q,
264
+ ...frontier, ...relationParams, ...entityTypes,
265
+ ...frontier, ...relationParams, ...entityTypes,
266
+ limit,
267
+ );
268
+ }
269
+ } else {
270
+ const frontierCondition = directed
271
+ ? inArray(memoryEntityRelations.sourceEntityId, frontier)
272
+ : or(
273
+ inArray(memoryEntityRelations.sourceEntityId, frontier),
274
+ inArray(memoryEntityRelations.targetEntityId, frontier),
275
+ );
276
+ const whereCondition = relationTypes && relationTypes.length > 0
277
+ ? and(frontierCondition, inArray(memoryEntityRelations.relation, relationTypes))
278
+ : frontierCondition;
279
+
280
+ rows = db
281
+ .select({
282
+ sourceEntityId: memoryEntityRelations.sourceEntityId,
283
+ targetEntityId: memoryEntityRelations.targetEntityId,
284
+ })
285
+ .from(memoryEntityRelations)
286
+ .where(whereCondition)
287
+ .orderBy(desc(memoryEntityRelations.lastSeenAt))
288
+ .limit(Math.max(1, edgeBudget))
289
+ .all();
290
+ }
190
291
 
191
292
  totalEdgesTraversed += rows.length;
192
293
 
@@ -194,16 +295,20 @@ export function findNeighborEntities(
194
295
  const frontierSet = new Set(frontier);
195
296
  for (const row of rows) {
196
297
  if (neighbors.length >= maxNeighborEntities) break;
298
+ // In directed mode, only follow source→target (frontier is always on source side)
197
299
  if (frontierSet.has(row.sourceEntityId) && !visited.has(row.targetEntityId)) {
198
300
  visited.add(row.targetEntityId);
199
301
  neighbors.push(row.targetEntityId);
200
302
  nextFrontier.push(row.targetEntityId);
303
+ neighborDepths.set(row.targetEntityId, depth + 1);
201
304
  }
305
+ if (directed) continue;
202
306
  if (neighbors.length >= maxNeighborEntities) break;
203
307
  if (frontierSet.has(row.targetEntityId) && !visited.has(row.sourceEntityId)) {
204
308
  visited.add(row.sourceEntityId);
205
309
  neighbors.push(row.sourceEntityId);
206
310
  nextFrontier.push(row.sourceEntityId);
311
+ neighborDepths.set(row.sourceEntityId, depth + 1);
207
312
  }
208
313
  }
209
314
 
@@ -213,6 +318,7 @@ export function findNeighborEntities(
213
318
  return {
214
319
  neighborEntityIds: neighbors.slice(0, maxNeighborEntities),
215
320
  traversedEdgeCount: totalEdgesTraversed,
321
+ neighborDepths,
216
322
  };
217
323
  }
218
324
 
@@ -296,3 +402,71 @@ export function getEntityLinkedItemCandidates(
296
402
  finalScore: 0,
297
403
  }));
298
404
  }
405
+
406
+ /**
407
+ * Multi-step typed traversal: each step expands the frontier through
408
+ * edges matching the step's relation/entity type filters.
409
+ * Returns entity IDs reachable after all steps are applied in sequence.
410
+ */
411
+ export function collectTypedNeighbors(
412
+ seedEntityIds: string[],
413
+ steps: TraversalStep[],
414
+ opts?: { maxResultsPerStep?: number; maxEdgesPerStep?: number },
415
+ ): string[] {
416
+ if (seedEntityIds.length === 0 || steps.length === 0) return [];
417
+
418
+ const maxResults = opts?.maxResultsPerStep ?? 20;
419
+ const maxEdges = opts?.maxEdgesPerStep ?? 40;
420
+
421
+ let currentSeeds = seedEntityIds;
422
+
423
+ for (const step of steps) {
424
+ if (currentSeeds.length === 0) break;
425
+
426
+ const result = findNeighborEntities(currentSeeds, {
427
+ maxEdges,
428
+ maxNeighborEntities: maxResults,
429
+ maxDepth: 1,
430
+ relationTypes: step.relationTypes,
431
+ entityTypes: step.entityTypes,
432
+ directed: true,
433
+ });
434
+
435
+ currentSeeds = result.neighborEntityIds;
436
+ }
437
+
438
+ return currentSeeds;
439
+ }
440
+
441
+ /**
442
+ * Find entities reachable from ALL given seeds via their respective
443
+ * typed traversal steps, then return the intersection.
444
+ */
445
+ export function intersectReachable(
446
+ queries: Array<{
447
+ seedEntityIds: string[];
448
+ steps: TraversalStep[];
449
+ }>,
450
+ opts?: { maxResultsPerStep?: number; maxEdgesPerStep?: number },
451
+ ): string[] {
452
+ if (queries.length === 0) return [];
453
+
454
+ const resultSets: Set<string>[] = [];
455
+ for (const query of queries) {
456
+ const result = collectTypedNeighbors(
457
+ query.seedEntityIds,
458
+ query.steps,
459
+ opts,
460
+ );
461
+ resultSets.push(new Set(result));
462
+ }
463
+
464
+ if (resultSets.length === 0) return [];
465
+
466
+ // Intersect all sets: keep only entities present in ALL sets
467
+ const intersection = [...resultSets[0]].filter(id =>
468
+ resultSets.every(set => set.has(id)),
469
+ );
470
+
471
+ return intersection;
472
+ }