@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
package/src/instrument.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as Sentry from "@sentry/node";
2
2
  import { APP_VERSION } from "./version.js";
3
+ import { getSentryDsn } from "./config/env.js";
3
4
 
4
5
  /** Patterns that match sensitive data in Sentry event values. */
5
6
  const PII_PATTERNS = [
@@ -20,7 +21,7 @@ function redactString(value: string): string {
20
21
  function redactObject(obj: unknown): unknown {
21
22
  if (typeof obj === "string") return redactString(obj);
22
23
  if (Array.isArray(obj)) return obj.map(redactObject);
23
- if (obj !== null && typeof obj === "object") {
24
+ if (obj != null && typeof obj === "object") {
24
25
  const out: Record<string, unknown> = {};
25
26
  for (const [key, val] of Object.entries(obj)) {
26
27
  out[key] = redactObject(val);
@@ -30,10 +31,10 @@ function redactObject(obj: unknown): unknown {
30
31
  return obj;
31
32
  }
32
33
 
33
- /** Call after dotenv has loaded so process.env.SENTRY_DSN is available. */
34
+ /** Call after dotenv has loaded so SENTRY_DSN is available. */
34
35
  export function initSentry(): void {
35
36
  Sentry.init({
36
- dsn: process.env.SENTRY_DSN,
37
+ dsn: getSentryDsn(),
37
38
  release: `vellum-assistant@${APP_VERSION}`,
38
39
  environment: APP_VERSION === "0.0.0-dev" ? "development" : "production",
39
40
  sendDefaultPii: false,
package/src/logfire.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Provider, ProviderResponse, SendMessageOptions, Message, ToolDefinition } from './providers/types.js';
2
2
  import { APP_VERSION } from './version.js';
3
3
  import { getLogger } from './util/logger.js';
4
+ import { getLogfireToken, isMonitoringEnabled } from './config/env.js';
4
5
 
5
6
  const log = getLogger('logfire');
6
7
 
@@ -8,8 +9,8 @@ type LogfireModule = typeof import('@pydantic/logfire-node');
8
9
 
9
10
  const LOGFIRE_ENABLED: boolean =
10
11
  APP_VERSION === '0.0.0-dev' &&
11
- !!process.env.LOGFIRE_TOKEN &&
12
- process.env.VELLUM_ENABLE_MONITORING === '1';
12
+ !!getLogfireToken() &&
13
+ isMonitoringEnabled();
13
14
 
14
15
  let logfireInstance: LogfireModule | null = null;
15
16
 
@@ -24,7 +25,7 @@ export async function initLogfire(): Promise<void> {
24
25
  try {
25
26
  const logfire = await import('@pydantic/logfire-node');
26
27
  logfire.configure({
27
- token: process.env.LOGFIRE_TOKEN,
28
+ token: getLogfireToken(),
28
29
  serviceName: 'vellum-assistant',
29
30
  serviceVersion: APP_VERSION,
30
31
  });
@@ -1,7 +1,7 @@
1
1
  import { getConfig } from '../config/loader.js';
2
2
  import { getLogger } from '../util/logger.js';
3
3
  import { getMemoryBackendStatus } from './embedding-backend.js';
4
- import { getDb } from './db.js';
4
+ import { rawGet } from './db.js';
5
5
  import { enqueueBackfillJob, enqueueRebuildIndexJob } from './indexer.js';
6
6
  import {
7
7
  enqueueCleanupResolvedConflictsJob,
@@ -43,28 +43,35 @@ export interface MemoryConflictAndCleanupStats {
43
43
  cleanup: MemorySystemStatus['cleanup'];
44
44
  }
45
45
 
46
+ interface ConflictStatsRow {
47
+ pending_count: number | null;
48
+ resolved_count: number | null;
49
+ oldest_pending_created_at: number | null;
50
+ }
51
+
52
+ interface CleanupStatsRow {
53
+ resolved_backlog: number | null;
54
+ superseded_backlog: number | null;
55
+ resolved_completed_24h: number | null;
56
+ superseded_completed_24h: number | null;
57
+ }
58
+
46
59
  /** Lightweight query for conflict/cleanup metrics only — no table counts or job totals. */
47
60
  export function getMemoryConflictAndCleanupStats(): MemoryConflictAndCleanupStats {
48
- const db = getDb();
49
- const raw = (db as unknown as { $client: { query: (q: string) => { get: (...args: unknown[]) => unknown } } }).$client;
50
- const conflictStats = raw.query(`
61
+ const conflictStats = rawGet<ConflictStatsRow>(`
51
62
  SELECT
52
63
  SUM(CASE WHEN status = 'pending_clarification' THEN 1 ELSE 0 END) AS pending_count,
53
64
  SUM(CASE WHEN status != 'pending_clarification' THEN 1 ELSE 0 END) AS resolved_count,
54
65
  MIN(CASE WHEN status = 'pending_clarification' THEN created_at END) AS oldest_pending_created_at
55
66
  FROM memory_item_conflicts
56
- `).get() as {
57
- pending_count: number | null;
58
- resolved_count: number | null;
59
- oldest_pending_created_at: number | null;
60
- } | null;
67
+ `);
61
68
  const pending = conflictStats?.pending_count ?? 0;
62
69
  const oldestPendingCreatedAt = conflictStats?.oldest_pending_created_at ?? null;
63
- const oldestPendingAgeMs = oldestPendingCreatedAt === null
70
+ const oldestPendingAgeMs = oldestPendingCreatedAt == null
64
71
  ? null
65
72
  : Math.max(0, Date.now() - oldestPendingCreatedAt);
66
73
  const throughputWindowStartMs = Date.now() - (24 * 60 * 60 * 1000);
67
- const cleanupStats = raw.query(`
74
+ const cleanupStats = rawGet<CleanupStatsRow>(`
68
75
  SELECT
69
76
  SUM(CASE
70
77
  WHEN type = 'cleanup_resolved_conflicts' AND status IN ('pending', 'running')
@@ -83,12 +90,7 @@ export function getMemoryConflictAndCleanupStats(): MemoryConflictAndCleanupStat
83
90
  THEN 1 ELSE 0 END
84
91
  ) AS superseded_completed_24h
85
92
  FROM memory_jobs
86
- `).get(throughputWindowStartMs, throughputWindowStartMs) as {
87
- resolved_backlog: number | null;
88
- superseded_backlog: number | null;
89
- resolved_completed_24h: number | null;
90
- superseded_completed_24h: number | null;
91
- } | null;
93
+ `, throughputWindowStartMs, throughputWindowStartMs);
92
94
  return {
93
95
  conflicts: {
94
96
  pending,
@@ -107,32 +109,26 @@ export function getMemoryConflictAndCleanupStats(): MemoryConflictAndCleanupStat
107
109
  export function getMemorySystemStatus(): MemorySystemStatus {
108
110
  const config = getConfig();
109
111
  const backend = getMemoryBackendStatus(config);
110
- const db = getDb();
111
- const raw = (db as unknown as { $client: { query: (q: string) => { get: (...args: unknown[]) => unknown } } }).$client;
112
112
  const counts = {
113
113
  segments: countTable('memory_segments'),
114
114
  items: countTable('memory_items'),
115
115
  summaries: countTable('memory_summaries'),
116
116
  embeddings: countTable('memory_embeddings'),
117
117
  };
118
- const conflictStats = raw.query(`
118
+ const conflictStats = rawGet<ConflictStatsRow>(`
119
119
  SELECT
120
120
  SUM(CASE WHEN status = 'pending_clarification' THEN 1 ELSE 0 END) AS pending_count,
121
121
  SUM(CASE WHEN status != 'pending_clarification' THEN 1 ELSE 0 END) AS resolved_count,
122
122
  MIN(CASE WHEN status = 'pending_clarification' THEN created_at END) AS oldest_pending_created_at
123
123
  FROM memory_item_conflicts
124
- `).get() as {
125
- pending_count: number | null;
126
- resolved_count: number | null;
127
- oldest_pending_created_at: number | null;
128
- } | null;
124
+ `);
129
125
  const pending = conflictStats?.pending_count ?? 0;
130
126
  const oldestPendingCreatedAt = conflictStats?.oldest_pending_created_at ?? null;
131
- const oldestPendingAgeMs = oldestPendingCreatedAt === null
127
+ const oldestPendingAgeMs = oldestPendingCreatedAt == null
132
128
  ? null
133
129
  : Math.max(0, Date.now() - oldestPendingCreatedAt);
134
130
  const throughputWindowStartMs = Date.now() - (24 * 60 * 60 * 1000);
135
- const cleanupStats = raw.query(`
131
+ const cleanupStats = rawGet<CleanupStatsRow>(`
136
132
  SELECT
137
133
  SUM(CASE
138
134
  WHEN type = 'cleanup_resolved_conflicts' AND status IN ('pending', 'running')
@@ -151,12 +147,7 @@ export function getMemorySystemStatus(): MemorySystemStatus {
151
147
  THEN 1 ELSE 0 END
152
148
  ) AS superseded_completed_24h
153
149
  FROM memory_jobs
154
- `).get(throughputWindowStartMs, throughputWindowStartMs) as {
155
- resolved_backlog: number | null;
156
- superseded_backlog: number | null;
157
- resolved_completed_24h: number | null;
158
- superseded_completed_24h: number | null;
159
- } | null;
150
+ `, throughputWindowStartMs, throughputWindowStartMs);
160
151
  return {
161
152
  enabled: backend.enabled,
162
153
  degraded: backend.degraded,
@@ -179,8 +170,7 @@ export function getMemorySystemStatus(): MemorySystemStatus {
179
170
  };
180
171
 
181
172
  function countTable(table: string): number {
182
- const row = raw.query(`SELECT COUNT(*) AS c FROM ${table}`).get() as { c: number } | null;
183
- return row?.c ?? 0;
173
+ return rawGet<{ c: number }>(`SELECT COUNT(*) AS c FROM ${table}`)?.c ?? 0;
184
174
  }
185
175
  }
186
176
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { eq } from 'drizzle-orm';
9
9
  import { v4 as uuid } from 'uuid';
10
- import { getDb } from './db.js';
10
+ import { getDb, rawRun } from './db.js';
11
11
  import { attachments, messageAttachments } from './schema.js';
12
12
 
13
13
  export interface StoredAttachment {
@@ -322,7 +322,7 @@ export function getAttachmentsForMessage(
322
322
 
323
323
  if (links.length === 0) return [];
324
324
 
325
- const ids = links.map((l) => l.attachmentId).filter((id): id is string => id !== null);
325
+ const ids = links.map((l) => l.attachmentId).filter((id): id is string => id != null);
326
326
  return getAttachmentsByIds(ids);
327
327
  }
328
328
 
@@ -386,12 +386,9 @@ export function getAttachmentById(
386
386
  */
387
387
  export function deleteOrphanAttachments(candidateIds: string[]): number {
388
388
  if (candidateIds.length === 0) return 0;
389
- const db = getDb();
390
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
391
389
  const placeholders = candidateIds.map(() => '?').join(', ');
392
- const stmt = raw.prepare(
390
+ return rawRun(
393
391
  `DELETE FROM attachments WHERE id IN (${placeholders}) AND id NOT IN (SELECT attachment_id FROM message_attachments)`,
392
+ ...candidateIds,
394
393
  );
395
- const result = stmt.run(...candidateIds);
396
- return result.changes;
397
394
  }
@@ -11,7 +11,7 @@
11
11
  * and a replay endpoint allows manual recovery of dead-lettered ones.
12
12
  */
13
13
 
14
- import { eq, and, lte, isNotNull } from 'drizzle-orm';
14
+ import { eq, and, desc, lte, isNotNull } from 'drizzle-orm';
15
15
  import { v4 as uuid } from 'uuid';
16
16
  import { getDb } from './db.js';
17
17
  import { channelInboundEvents, conversations } from './schema.js';
@@ -229,6 +229,35 @@ export function clearPayload(eventId: string): void {
229
229
  .run();
230
230
  }
231
231
 
232
+ /**
233
+ * Retrieve the stored raw payload for a given conversation's most recent
234
+ * inbound event. Used by the escalation decide flow to recover the
235
+ * original message content after an approve/deny decision.
236
+ */
237
+ export function getLatestStoredPayload(conversationId: string): Record<string, unknown> | null {
238
+ const db = getDb();
239
+ const row = db
240
+ .select({
241
+ rawPayload: channelInboundEvents.rawPayload,
242
+ })
243
+ .from(channelInboundEvents)
244
+ .where(
245
+ and(
246
+ eq(channelInboundEvents.conversationId, conversationId),
247
+ isNotNull(channelInboundEvents.rawPayload),
248
+ ),
249
+ )
250
+ .orderBy(desc(channelInboundEvents.createdAt))
251
+ .get();
252
+
253
+ if (!row?.rawPayload) return null;
254
+ try {
255
+ return JSON.parse(row.rawPayload) as Record<string, unknown>;
256
+ } catch {
257
+ return null;
258
+ }
259
+ }
260
+
232
261
  /** Mark an event as successfully processed. */
233
262
  export function markProcessed(eventId: string): void {
234
263
  const db = getDb();
@@ -8,7 +8,7 @@
8
8
  * requests track per-run guardian approval decisions.
9
9
  */
10
10
 
11
- import { and, desc, eq, gt, lte } from 'drizzle-orm';
11
+ import { and, count, desc, eq, gt, lte } from 'drizzle-orm';
12
12
  import { v4 as uuid } from 'uuid';
13
13
  import { getDb } from './db.js';
14
14
  import {
@@ -142,6 +142,7 @@ export function createBinding(params: {
142
142
  guardianExternalUserId: string;
143
143
  guardianDeliveryChatId: string;
144
144
  verifiedVia?: string;
145
+ metadataJson?: string | null;
145
146
  }): GuardianBinding {
146
147
  const db = getDb();
147
148
  const now = Date.now();
@@ -156,7 +157,7 @@ export function createBinding(params: {
156
157
  status: 'active' as const,
157
158
  verifiedAt: now,
158
159
  verifiedVia: params.verifiedVia ?? 'challenge',
159
- metadataJson: null,
160
+ metadataJson: params.metadataJson ?? null,
160
161
  createdAt: now,
161
162
  updatedAt: now,
162
163
  };
@@ -256,6 +257,20 @@ export function createChallenge(params: {
256
257
  return rowToChallenge(row);
257
258
  }
258
259
 
260
+ export function revokePendingChallenges(assistantId: string, channel: string): void {
261
+ const db = getDb();
262
+ db.update(channelGuardianVerificationChallenges)
263
+ .set({ status: 'revoked', updatedAt: Date.now() })
264
+ .where(
265
+ and(
266
+ eq(channelGuardianVerificationChallenges.assistantId, assistantId),
267
+ eq(channelGuardianVerificationChallenges.channel, channel),
268
+ eq(channelGuardianVerificationChallenges.status, 'pending'),
269
+ ),
270
+ )
271
+ .run();
272
+ }
273
+
259
274
  export function findPendingChallengeByHash(
260
275
  assistantId: string,
261
276
  channel: string,
@@ -281,6 +296,33 @@ export function findPendingChallengeByHash(
281
296
  return row ? rowToChallenge(row) : null;
282
297
  }
283
298
 
299
+ /**
300
+ * Find any pending (non-expired) challenge for a given (assistantId, channel).
301
+ * Used by relay setup to detect whether a voice verification session is active.
302
+ */
303
+ export function findPendingChallengeForChannel(
304
+ assistantId: string,
305
+ channel: string,
306
+ ): VerificationChallenge | null {
307
+ const db = getDb();
308
+ const now = Date.now();
309
+
310
+ const row = db
311
+ .select()
312
+ .from(channelGuardianVerificationChallenges)
313
+ .where(
314
+ and(
315
+ eq(channelGuardianVerificationChallenges.assistantId, assistantId),
316
+ eq(channelGuardianVerificationChallenges.channel, channel),
317
+ eq(channelGuardianVerificationChallenges.status, 'pending'),
318
+ gt(channelGuardianVerificationChallenges.expiresAt, now),
319
+ ),
320
+ )
321
+ .get();
322
+
323
+ return row ? rowToChallenge(row) : null;
324
+ }
325
+
284
326
  export function consumeChallenge(
285
327
  id: string,
286
328
  consumedByExternalUserId: string,
@@ -538,6 +580,164 @@ export function updateApprovalDecision(
538
580
  .run();
539
581
  }
540
582
 
583
+ // ---------------------------------------------------------------------------
584
+ // Inbox / Escalation Query Helpers
585
+ // ---------------------------------------------------------------------------
586
+
587
+ /**
588
+ * List approval requests filtered by assistant, and optionally by channel,
589
+ * conversation, and status. Designed for the inbox UI to show a paginated
590
+ * list of escalations.
591
+ */
592
+ export function listPendingApprovalRequests(params: {
593
+ assistantId?: string;
594
+ channel?: string;
595
+ conversationId?: string;
596
+ status?: ApprovalRequestStatus;
597
+ limit?: number;
598
+ offset?: number;
599
+ }): GuardianApprovalRequest[] {
600
+ const db = getDb();
601
+
602
+ const conditions = [
603
+ eq(channelGuardianApprovalRequests.assistantId, params.assistantId ?? 'self'),
604
+ ];
605
+ if (params.channel) {
606
+ conditions.push(eq(channelGuardianApprovalRequests.channel, params.channel));
607
+ }
608
+ if (params.conversationId) {
609
+ conditions.push(eq(channelGuardianApprovalRequests.conversationId, params.conversationId));
610
+ }
611
+ conditions.push(
612
+ eq(channelGuardianApprovalRequests.status, params.status ?? 'pending'),
613
+ );
614
+
615
+ let query = db
616
+ .select()
617
+ .from(channelGuardianApprovalRequests)
618
+ .where(and(...conditions))
619
+ .orderBy(desc(channelGuardianApprovalRequests.createdAt));
620
+
621
+ if (params.limit !== undefined) {
622
+ query = query.limit(params.limit) as typeof query;
623
+ }
624
+ if (params.offset !== undefined) {
625
+ query = query.offset(params.offset) as typeof query;
626
+ }
627
+
628
+ return query.all().map(rowToApprovalRequest);
629
+ }
630
+
631
+ /**
632
+ * Fetch a single approval request by its primary key.
633
+ */
634
+ export function getApprovalRequestById(id: string): GuardianApprovalRequest | null {
635
+ const db = getDb();
636
+
637
+ const row = db
638
+ .select()
639
+ .from(channelGuardianApprovalRequests)
640
+ .where(eq(channelGuardianApprovalRequests.id, id))
641
+ .get();
642
+
643
+ return row ? rowToApprovalRequest(row) : null;
644
+ }
645
+
646
+ /**
647
+ * Fetch a single approval request by run ID (any status).
648
+ * Useful for checking whether a run has an associated approval request.
649
+ */
650
+ export function getApprovalRequestByRunId(runId: string): GuardianApprovalRequest | null {
651
+ const db = getDb();
652
+
653
+ const row = db
654
+ .select()
655
+ .from(channelGuardianApprovalRequests)
656
+ .where(eq(channelGuardianApprovalRequests.runId, runId))
657
+ .orderBy(desc(channelGuardianApprovalRequests.createdAt))
658
+ .get();
659
+
660
+ return row ? rowToApprovalRequest(row) : null;
661
+ }
662
+
663
+ /**
664
+ * Resolve a pending approval request with a decision.
665
+ *
666
+ * Idempotent: if the request is already resolved with the same decision,
667
+ * the existing record is returned unchanged. Returns null if the request
668
+ * does not exist or was resolved with a *different* decision.
669
+ */
670
+ export function resolveApprovalRequest(
671
+ id: string,
672
+ decision: 'approved' | 'denied',
673
+ decidedByExternalUserId?: string,
674
+ ): GuardianApprovalRequest | null {
675
+ const db = getDb();
676
+
677
+ const existing = db
678
+ .select()
679
+ .from(channelGuardianApprovalRequests)
680
+ .where(eq(channelGuardianApprovalRequests.id, id))
681
+ .get();
682
+
683
+ if (!existing) return null;
684
+
685
+ // Idempotent: already resolved with the same decision
686
+ if (existing.status === decision) {
687
+ return rowToApprovalRequest(existing);
688
+ }
689
+
690
+ // Only resolve if currently pending
691
+ if (existing.status !== 'pending') {
692
+ return null;
693
+ }
694
+
695
+ const now = Date.now();
696
+
697
+ db.update(channelGuardianApprovalRequests)
698
+ .set({
699
+ status: decision,
700
+ decidedByExternalUserId: decidedByExternalUserId ?? null,
701
+ updatedAt: now,
702
+ })
703
+ .where(eq(channelGuardianApprovalRequests.id, id))
704
+ .run();
705
+
706
+ return rowToApprovalRequest({
707
+ ...existing,
708
+ status: decision,
709
+ decidedByExternalUserId: decidedByExternalUserId ?? null,
710
+ updatedAt: now,
711
+ });
712
+ }
713
+
714
+ /**
715
+ * Count pending approval requests for a given conversation.
716
+ * Used by thread state projection to compute `pending_escalation_count`.
717
+ */
718
+ export function countPendingByConversation(
719
+ conversationId: string,
720
+ assistantId?: string,
721
+ ): number {
722
+ const db = getDb();
723
+
724
+ const conditions = [
725
+ eq(channelGuardianApprovalRequests.conversationId, conversationId),
726
+ eq(channelGuardianApprovalRequests.status, 'pending'),
727
+ ];
728
+ if (assistantId) {
729
+ conditions.push(eq(channelGuardianApprovalRequests.assistantId, assistantId));
730
+ }
731
+
732
+ const result = db
733
+ .select({ count: count() })
734
+ .from(channelGuardianApprovalRequests)
735
+ .where(and(...conditions))
736
+ .get();
737
+
738
+ return result?.count ?? 0;
739
+ }
740
+
541
741
  // ---------------------------------------------------------------------------
542
742
  // Verification Rate Limits
543
743
  // ---------------------------------------------------------------------------
@@ -1,8 +1,8 @@
1
- import Anthropic from '@anthropic-ai/sdk';
2
- import { getConfig } from '../config/loader.js';
1
+ import { getConfiguredProvider, createTimeout, extractToolUse, userMessage } from '../providers/provider-send-message.js';
2
+ import type { ModelIntent } from '../providers/types.js';
3
3
  import { truncate } from '../util/truncate.js';
4
4
 
5
- const DEFAULT_RESOLVER_MODEL = 'claude-haiku-4-5-20251001';
5
+ const DEFAULT_RESOLVER_MODEL_INTENT: ModelIntent = 'latency-optimized';
6
6
  const DEFAULT_RESOLVER_TIMEOUT_MS = 12_000;
7
7
 
8
8
  const DIRECTIONAL_EXISTING_CUES = ['existing', 'old', 'previous', 'first', 'earlier', 'original'];
@@ -38,6 +38,7 @@ export interface ClarificationResolverInput {
38
38
  export interface ClarificationResolverOptions {
39
39
  apiKey?: string;
40
40
  model?: string;
41
+ modelIntent?: ModelIntent;
41
42
  timeoutMs?: number;
42
43
  }
43
44
 
@@ -55,21 +56,20 @@ export async function resolveConflictClarification(
55
56
  const heuristicResult = resolveWithHeuristics(input);
56
57
  if (heuristicResult) return heuristicResult;
57
58
 
58
- const config = getConfig();
59
- const apiKey = options?.apiKey ?? config.apiKeys.anthropic ?? process.env.ANTHROPIC_API_KEY;
60
- if (!apiKey) {
59
+ const provider = getConfiguredProvider();
60
+ if (!provider) {
61
61
  return {
62
62
  resolution: 'still_unclear',
63
63
  strategy: 'no_llm_key',
64
64
  resolvedStatement: null,
65
- explanation: 'No Anthropic API key available for clarification fallback.',
65
+ explanation: 'Configured provider unavailable for clarification fallback.',
66
66
  };
67
67
  }
68
68
 
69
69
  try {
70
70
  return await resolveWithLlm(input, {
71
- apiKey,
72
- model: options?.model ?? DEFAULT_RESOLVER_MODEL,
71
+ model: options?.model,
72
+ modelIntent: options?.modelIntent ?? DEFAULT_RESOLVER_MODEL_INTENT,
73
73
  timeoutMs: options?.timeoutMs ?? DEFAULT_RESOLVER_TIMEOUT_MS,
74
74
  });
75
75
  } catch (err) {
@@ -168,9 +168,9 @@ function resolveWithHeuristics(input: ClarificationResolverInput): Clarification
168
168
 
169
169
  async function resolveWithLlm(
170
170
  input: ClarificationResolverInput,
171
- options: { apiKey: string; model: string; timeoutMs: number },
171
+ options: { model?: string; modelIntent: ModelIntent; timeoutMs: number },
172
172
  ): Promise<ClarificationResolverResult> {
173
- const client = new Anthropic({ apiKey: options.apiKey });
173
+ const provider = getConfiguredProvider()!;
174
174
  const userPrompt = [
175
175
  'You are resolving a memory clarification response.',
176
176
  '',
@@ -179,22 +179,12 @@ async function resolveWithLlm(
179
179
  `User clarification: ${input.userMessage}`,
180
180
  ].join('\n');
181
181
 
182
- const abortController = new AbortController();
183
- const timer = setTimeout(() => abortController.abort(), options.timeoutMs);
182
+ const { signal, cleanup } = createTimeout(options.timeoutMs);
184
183
 
185
184
  try {
186
- const response = await client.messages.create({
187
- model: options.model,
188
- max_tokens: 256,
189
- system: [
190
- 'Classify the user clarification for conflicting memory statements.',
191
- 'Return exactly one resolution:',
192
- '- keep_existing',
193
- '- keep_candidate',
194
- '- merge',
195
- '- still_unclear',
196
- ].join('\n'),
197
- tools: [{
185
+ const response = await provider.sendMessage(
186
+ [userMessage(userPrompt)],
187
+ [{
198
188
  name: 'resolve_conflict_clarification',
199
189
  description: 'Resolve a pending memory contradiction using user clarification.',
200
190
  input_schema: {
@@ -216,13 +206,27 @@ async function resolveWithLlm(
216
206
  required: ['resolution', 'explanation'],
217
207
  },
218
208
  }],
219
- tool_choice: { type: 'tool' as const, name: 'resolve_conflict_clarification' },
220
- messages: [{ role: 'user' as const, content: userPrompt }],
221
- }, { signal: abortController.signal });
222
- clearTimeout(timer);
209
+ [
210
+ 'Classify the user clarification for conflicting memory statements.',
211
+ 'Return exactly one resolution:',
212
+ '- keep_existing',
213
+ '- keep_candidate',
214
+ '- merge',
215
+ '- still_unclear',
216
+ ].join('\n'),
217
+ {
218
+ config: {
219
+ ...(options.model ? { model: options.model } : { modelIntent: options.modelIntent }),
220
+ max_tokens: 256,
221
+ tool_choice: { type: 'tool' as const, name: 'resolve_conflict_clarification' },
222
+ },
223
+ signal,
224
+ },
225
+ );
226
+ cleanup();
223
227
 
224
- const toolBlock = response.content.find((block) => block.type === 'tool_use');
225
- if (!toolBlock || toolBlock.type !== 'tool_use') {
228
+ const toolBlock = extractToolUse(response);
229
+ if (!toolBlock) {
226
230
  throw new Error('No tool_use block in clarification resolver response.');
227
231
  }
228
232
 
@@ -247,8 +251,8 @@ async function resolveWithLlm(
247
251
  explanation: truncate(normalize(parsed.explanation ?? 'Resolved via LLM fallback.'), 500, ''),
248
252
  };
249
253
  } catch (err) {
250
- clearTimeout(timer);
251
- if (abortController.signal.aborted) {
254
+ cleanup();
255
+ if (signal.aborted) {
252
256
  throw new Error('clarification_resolver_timeout');
253
257
  }
254
258
  throw err;