@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
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Store for cross-channel guardian action requests and deliveries.
3
+ *
4
+ * Guardian action requests are created when a voice call's ASK_GUARDIAN
5
+ * marker fires, and deliveries track per-channel dispatch (telegram, sms, mac).
6
+ * Resolution uses first-response-wins semantics: the first channel to
7
+ * answer resolves the request and all other deliveries are marked answered.
8
+ */
9
+
10
+ import { and, eq, lt, inArray } from 'drizzle-orm';
11
+ import { v4 as uuid } from 'uuid';
12
+ import { getDb, rawChanges } from './db.js';
13
+ import {
14
+ guardianActionRequests,
15
+ guardianActionDeliveries,
16
+ } from './schema.js';
17
+ import { getLogger } from '../util/logger.js';
18
+
19
+ const log = getLogger('guardian-action-store');
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Types
23
+ // ---------------------------------------------------------------------------
24
+
25
+ export type GuardianActionRequestStatus = 'pending' | 'answered' | 'expired' | 'cancelled';
26
+ export type GuardianActionDeliveryStatus = 'pending' | 'sent' | 'failed' | 'answered' | 'expired' | 'cancelled';
27
+
28
+ export interface GuardianActionRequest {
29
+ id: string;
30
+ assistantId: string;
31
+ kind: string;
32
+ sourceChannel: string;
33
+ sourceConversationId: string;
34
+ callSessionId: string;
35
+ pendingQuestionId: string;
36
+ questionText: string;
37
+ requestCode: string;
38
+ status: GuardianActionRequestStatus;
39
+ answerText: string | null;
40
+ answeredByChannel: string | null;
41
+ answeredByExternalUserId: string | null;
42
+ answeredAt: number | null;
43
+ expiresAt: number;
44
+ createdAt: number;
45
+ updatedAt: number;
46
+ }
47
+
48
+ export interface GuardianActionDelivery {
49
+ id: string;
50
+ requestId: string;
51
+ destinationChannel: string;
52
+ destinationConversationId: string | null;
53
+ destinationChatId: string | null;
54
+ destinationExternalUserId: string | null;
55
+ status: GuardianActionDeliveryStatus;
56
+ sentAt: number | null;
57
+ respondedAt: number | null;
58
+ lastError: string | null;
59
+ createdAt: number;
60
+ updatedAt: number;
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Helpers
65
+ // ---------------------------------------------------------------------------
66
+
67
+ function rowToRequest(row: typeof guardianActionRequests.$inferSelect): GuardianActionRequest {
68
+ return {
69
+ id: row.id,
70
+ assistantId: row.assistantId,
71
+ kind: row.kind,
72
+ sourceChannel: row.sourceChannel,
73
+ sourceConversationId: row.sourceConversationId,
74
+ callSessionId: row.callSessionId,
75
+ pendingQuestionId: row.pendingQuestionId,
76
+ questionText: row.questionText,
77
+ requestCode: row.requestCode,
78
+ status: row.status as GuardianActionRequestStatus,
79
+ answerText: row.answerText,
80
+ answeredByChannel: row.answeredByChannel,
81
+ answeredByExternalUserId: row.answeredByExternalUserId,
82
+ answeredAt: row.answeredAt,
83
+ expiresAt: row.expiresAt,
84
+ createdAt: row.createdAt,
85
+ updatedAt: row.updatedAt,
86
+ };
87
+ }
88
+
89
+ function rowToDelivery(row: typeof guardianActionDeliveries.$inferSelect): GuardianActionDelivery {
90
+ return {
91
+ id: row.id,
92
+ requestId: row.requestId,
93
+ destinationChannel: row.destinationChannel,
94
+ destinationConversationId: row.destinationConversationId,
95
+ destinationChatId: row.destinationChatId,
96
+ destinationExternalUserId: row.destinationExternalUserId,
97
+ status: row.status as GuardianActionDeliveryStatus,
98
+ sentAt: row.sentAt,
99
+ respondedAt: row.respondedAt,
100
+ lastError: row.lastError,
101
+ createdAt: row.createdAt,
102
+ updatedAt: row.updatedAt,
103
+ };
104
+ }
105
+
106
+ /** Generate a short human-readable request code (6 hex chars). */
107
+ function generateRequestCode(): string {
108
+ return uuid().replace(/-/g, '').slice(0, 6).toUpperCase();
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Guardian Action Requests
113
+ // ---------------------------------------------------------------------------
114
+
115
+ export function createGuardianActionRequest(params: {
116
+ assistantId?: string;
117
+ kind: string;
118
+ sourceChannel: string;
119
+ sourceConversationId: string;
120
+ callSessionId: string;
121
+ pendingQuestionId: string;
122
+ questionText: string;
123
+ expiresAt: number;
124
+ }): GuardianActionRequest {
125
+ const db = getDb();
126
+ const now = Date.now();
127
+ const id = uuid();
128
+
129
+ const row = {
130
+ id,
131
+ assistantId: params.assistantId ?? 'self',
132
+ kind: params.kind,
133
+ sourceChannel: params.sourceChannel,
134
+ sourceConversationId: params.sourceConversationId,
135
+ callSessionId: params.callSessionId,
136
+ pendingQuestionId: params.pendingQuestionId,
137
+ questionText: params.questionText,
138
+ requestCode: generateRequestCode(),
139
+ status: 'pending' as const,
140
+ answerText: null,
141
+ answeredByChannel: null,
142
+ answeredByExternalUserId: null,
143
+ answeredAt: null,
144
+ expiresAt: params.expiresAt,
145
+ createdAt: now,
146
+ updatedAt: now,
147
+ };
148
+
149
+ db.insert(guardianActionRequests).values(row).run();
150
+ return rowToRequest(row);
151
+ }
152
+
153
+ export function getGuardianActionRequest(id: string): GuardianActionRequest | null {
154
+ const db = getDb();
155
+ const row = db
156
+ .select()
157
+ .from(guardianActionRequests)
158
+ .where(eq(guardianActionRequests.id, id))
159
+ .get();
160
+ return row ? rowToRequest(row) : null;
161
+ }
162
+
163
+ export function getByPendingQuestionId(questionId: string): GuardianActionRequest | null {
164
+ const db = getDb();
165
+ const row = db
166
+ .select()
167
+ .from(guardianActionRequests)
168
+ .where(eq(guardianActionRequests.pendingQuestionId, questionId))
169
+ .get();
170
+ return row ? rowToRequest(row) : null;
171
+ }
172
+
173
+ /**
174
+ * First-response-wins resolution. Checks that the request is still
175
+ * 'pending' before updating; returns the updated request on success
176
+ * or null if the request was already resolved.
177
+ */
178
+ export function resolveGuardianActionRequest(
179
+ id: string,
180
+ answerText: string,
181
+ answeredByChannel: string,
182
+ answeredByExternalUserId?: string,
183
+ ): GuardianActionRequest | null {
184
+ const db = getDb();
185
+ const now = Date.now();
186
+
187
+ // Atomically check-and-update: only update if status is still 'pending'
188
+ db.update(guardianActionRequests)
189
+ .set({
190
+ status: 'answered',
191
+ answerText,
192
+ answeredByChannel,
193
+ answeredByExternalUserId: answeredByExternalUserId ?? null,
194
+ answeredAt: now,
195
+ updatedAt: now,
196
+ })
197
+ .where(
198
+ and(
199
+ eq(guardianActionRequests.id, id),
200
+ eq(guardianActionRequests.status, 'pending'),
201
+ ),
202
+ )
203
+ .run();
204
+
205
+ // Check if the update took effect
206
+ if (rawChanges() === 0) return null;
207
+
208
+ // Mark all deliveries as 'answered'
209
+ db.update(guardianActionDeliveries)
210
+ .set({ status: 'answered', respondedAt: now, updatedAt: now })
211
+ .where(eq(guardianActionDeliveries.requestId, id))
212
+ .run();
213
+
214
+ return getGuardianActionRequest(id);
215
+ }
216
+
217
+ /**
218
+ * Expire a guardian action request and all its deliveries.
219
+ */
220
+ export function expireGuardianActionRequest(id: string): void {
221
+ const db = getDb();
222
+ const now = Date.now();
223
+
224
+ db.update(guardianActionRequests)
225
+ .set({ status: 'expired', updatedAt: now })
226
+ .where(
227
+ and(
228
+ eq(guardianActionRequests.id, id),
229
+ eq(guardianActionRequests.status, 'pending'),
230
+ ),
231
+ )
232
+ .run();
233
+
234
+ db.update(guardianActionDeliveries)
235
+ .set({ status: 'expired', updatedAt: now })
236
+ .where(
237
+ and(
238
+ eq(guardianActionDeliveries.requestId, id),
239
+ inArray(guardianActionDeliveries.status, ['pending', 'sent']),
240
+ ),
241
+ )
242
+ .run();
243
+ }
244
+
245
+ /**
246
+ * Get all pending guardian action requests that have expired.
247
+ */
248
+ export function getExpiredGuardianActionRequests(): GuardianActionRequest[] {
249
+ const db = getDb();
250
+ const now = Date.now();
251
+ return db
252
+ .select()
253
+ .from(guardianActionRequests)
254
+ .where(
255
+ and(
256
+ eq(guardianActionRequests.status, 'pending'),
257
+ lt(guardianActionRequests.expiresAt, now),
258
+ ),
259
+ )
260
+ .all()
261
+ .map(rowToRequest);
262
+ }
263
+
264
+ /**
265
+ * Get all deliveries for a specific request.
266
+ */
267
+ export function getDeliveriesByRequestId(requestId: string): GuardianActionDelivery[] {
268
+ const db = getDb();
269
+ return db
270
+ .select()
271
+ .from(guardianActionDeliveries)
272
+ .where(eq(guardianActionDeliveries.requestId, requestId))
273
+ .all()
274
+ .map(rowToDelivery);
275
+ }
276
+
277
+ /**
278
+ * Cancel a guardian action request and all its deliveries.
279
+ */
280
+ export function cancelGuardianActionRequest(id: string): void {
281
+ const db = getDb();
282
+ const now = Date.now();
283
+
284
+ db.update(guardianActionRequests)
285
+ .set({ status: 'cancelled', updatedAt: now })
286
+ .where(
287
+ and(
288
+ eq(guardianActionRequests.id, id),
289
+ eq(guardianActionRequests.status, 'pending'),
290
+ ),
291
+ )
292
+ .run();
293
+
294
+ db.update(guardianActionDeliveries)
295
+ .set({ status: 'cancelled', updatedAt: now })
296
+ .where(
297
+ and(
298
+ eq(guardianActionDeliveries.requestId, id),
299
+ inArray(guardianActionDeliveries.status, ['pending', 'sent']),
300
+ ),
301
+ )
302
+ .run();
303
+ }
304
+
305
+ // ---------------------------------------------------------------------------
306
+ // Guardian Action Deliveries
307
+ // ---------------------------------------------------------------------------
308
+
309
+ export function createGuardianActionDelivery(params: {
310
+ requestId: string;
311
+ destinationChannel: string;
312
+ destinationConversationId?: string;
313
+ destinationChatId?: string;
314
+ destinationExternalUserId?: string;
315
+ }): GuardianActionDelivery {
316
+ const db = getDb();
317
+ const now = Date.now();
318
+ const id = uuid();
319
+
320
+ const row = {
321
+ id,
322
+ requestId: params.requestId,
323
+ destinationChannel: params.destinationChannel,
324
+ destinationConversationId: params.destinationConversationId ?? null,
325
+ destinationChatId: params.destinationChatId ?? null,
326
+ destinationExternalUserId: params.destinationExternalUserId ?? null,
327
+ status: 'pending' as const,
328
+ sentAt: null,
329
+ respondedAt: null,
330
+ lastError: null,
331
+ createdAt: now,
332
+ updatedAt: now,
333
+ };
334
+
335
+ db.insert(guardianActionDeliveries).values(row).run();
336
+ return rowToDelivery(row);
337
+ }
338
+
339
+ /**
340
+ * Look up pending deliveries for a specific destination.
341
+ * Used by inbound message routing to match incoming answers to deliveries.
342
+ */
343
+ export function getPendingDeliveriesByDestination(
344
+ assistantId: string,
345
+ channel: string,
346
+ chatId: string,
347
+ ): GuardianActionDelivery[] {
348
+ try {
349
+ const db = getDb();
350
+
351
+ // Join deliveries with requests to filter by assistantId
352
+ const rows = db
353
+ .select({
354
+ delivery: guardianActionDeliveries,
355
+ })
356
+ .from(guardianActionDeliveries)
357
+ .innerJoin(
358
+ guardianActionRequests,
359
+ eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
360
+ )
361
+ .where(
362
+ and(
363
+ eq(guardianActionRequests.assistantId, assistantId),
364
+ eq(guardianActionRequests.status, 'pending'),
365
+ eq(guardianActionDeliveries.destinationChannel, channel),
366
+ eq(guardianActionDeliveries.destinationChatId, chatId),
367
+ eq(guardianActionDeliveries.status, 'sent'),
368
+ ),
369
+ )
370
+ .all();
371
+
372
+ return rows.map((r) => rowToDelivery(r.delivery));
373
+ } catch (err) {
374
+ if (err instanceof Error && err.message.includes('no such table')) {
375
+ log.warn({ err }, 'guardian tables not yet created');
376
+ return [];
377
+ }
378
+ throw err;
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Look up a pending delivery by destination conversation ID (for mac channel routing).
384
+ */
385
+ export function getPendingDeliveryByConversation(conversationId: string): GuardianActionDelivery | null {
386
+ try {
387
+ const db = getDb();
388
+ const rows = db
389
+ .select({ delivery: guardianActionDeliveries })
390
+ .from(guardianActionDeliveries)
391
+ .innerJoin(
392
+ guardianActionRequests,
393
+ eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
394
+ )
395
+ .where(
396
+ and(
397
+ eq(guardianActionDeliveries.destinationConversationId, conversationId),
398
+ eq(guardianActionDeliveries.status, 'sent'),
399
+ eq(guardianActionRequests.status, 'pending'),
400
+ ),
401
+ )
402
+ .all();
403
+ return rows.length > 0 ? rowToDelivery(rows[0].delivery) : null;
404
+ } catch (err) {
405
+ if (err instanceof Error && err.message.includes('no such table')) {
406
+ log.warn({ err }, 'guardian tables not yet created');
407
+ return null;
408
+ }
409
+ throw err;
410
+ }
411
+ }
412
+
413
+ export function updateDeliveryStatus(
414
+ deliveryId: string,
415
+ status: GuardianActionDeliveryStatus,
416
+ error?: string,
417
+ ): void {
418
+ const db = getDb();
419
+ const now = Date.now();
420
+
421
+ const updates: Record<string, unknown> = { status, updatedAt: now };
422
+ if (status === 'sent') updates.sentAt = now;
423
+ if (status === 'answered') updates.respondedAt = now;
424
+ if (error !== undefined) updates.lastError = error;
425
+
426
+ db.update(guardianActionDeliveries)
427
+ .set(updates)
428
+ .where(eq(guardianActionDeliveries.id, deliveryId))
429
+ .run();
430
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Projects escalation state from channel_guardian_approval_requests onto
3
+ * assistant_inbox_thread_state.
4
+ *
5
+ * Keeps the inbox UI's escalation badges (pending_escalation_count,
6
+ * has_pending_escalation) in sync with the current set of pending
7
+ * guardian approval requests.
8
+ */
9
+
10
+ import { countPendingByConversation } from './channel-guardian-store.js';
11
+ import { updateEscalationState } from './inbox-thread-store.js';
12
+ import { getDb } from './db.js';
13
+ import { getSqliteFrom } from './db-connection.js';
14
+
15
+ /**
16
+ * Recompute pending escalation counts for all inbox threads and update
17
+ * the thread state rows. Called periodically or after approval decisions
18
+ * to keep the inbox UI's escalation badges accurate.
19
+ *
20
+ * Uses a single SQL UPDATE to set counts for all threads in one shot,
21
+ * handling both new escalations and cleared ones atomically.
22
+ */
23
+ export function refreshEscalationProjection(assistantId: string = 'self'): void {
24
+ const db = getDb();
25
+ const sqlite = getSqliteFrom(db);
26
+ const now = Date.now();
27
+
28
+ sqlite
29
+ .query(
30
+ `UPDATE assistant_inbox_thread_state
31
+ SET pending_escalation_count = (
32
+ SELECT COUNT(*) FROM channel_guardian_approval_requests
33
+ WHERE channel_guardian_approval_requests.conversation_id = assistant_inbox_thread_state.conversation_id
34
+ AND channel_guardian_approval_requests.status = 'pending'
35
+ AND channel_guardian_approval_requests.assistant_id = ?1
36
+ ),
37
+ has_pending_escalation = CASE WHEN (
38
+ SELECT COUNT(*) FROM channel_guardian_approval_requests
39
+ WHERE channel_guardian_approval_requests.conversation_id = assistant_inbox_thread_state.conversation_id
40
+ AND channel_guardian_approval_requests.status = 'pending'
41
+ AND channel_guardian_approval_requests.assistant_id = ?1
42
+ ) > 0 THEN 1 ELSE 0 END,
43
+ updated_at = ?2
44
+ WHERE assistant_id = ?1`,
45
+ )
46
+ .run(assistantId, now);
47
+ }
48
+
49
+ /**
50
+ * Refresh escalation state for a single thread. More efficient than
51
+ * refreshing all threads when only one conversation changed.
52
+ */
53
+ export function refreshThreadEscalation(
54
+ conversationId: string,
55
+ assistantId: string = 'self',
56
+ ): void {
57
+ const pendingCount = countPendingByConversation(conversationId, assistantId);
58
+ updateEscalationState(conversationId, pendingCount);
59
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Query helpers for the assistant_inbox_thread_state table.
3
+ *
4
+ * Provides CRUD operations for inbox thread state — the denormalized
5
+ * view that powers the assistant's inbox UI with per-thread metadata
6
+ * (unread counts, escalation state, last activity timestamps).
7
+ */
8
+
9
+ import { and, eq, sql } from 'drizzle-orm';
10
+ import { getDb } from './db.js';
11
+ import { assistantInboxThreadState } from './schema.js';
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Types
15
+ // ---------------------------------------------------------------------------
16
+
17
+ export type InboxThread = typeof assistantInboxThreadState.$inferSelect;
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Queries
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /**
24
+ * List inbox threads ordered by most recent activity.
25
+ * NULL last_message_at values sort last.
26
+ */
27
+ export function listThreads(params?: {
28
+ assistantId?: string;
29
+ limit?: number;
30
+ offset?: number;
31
+ }): InboxThread[] {
32
+ const db = getDb();
33
+ const assistantId = params?.assistantId ?? 'self';
34
+ const limit = params?.limit ?? 50;
35
+ const offset = params?.offset ?? 0;
36
+
37
+ return db
38
+ .select()
39
+ .from(assistantInboxThreadState)
40
+ .where(eq(assistantInboxThreadState.assistantId, assistantId))
41
+ .orderBy(sql`${assistantInboxThreadState.lastMessageAt} DESC NULLS LAST`)
42
+ .limit(limit)
43
+ .offset(offset)
44
+ .all();
45
+ }
46
+
47
+ /**
48
+ * Get a single thread by its conversation ID (the primary key).
49
+ */
50
+ export function getThread(conversationId: string): InboxThread | null {
51
+ const db = getDb();
52
+ const row = db
53
+ .select()
54
+ .from(assistantInboxThreadState)
55
+ .where(eq(assistantInboxThreadState.conversationId, conversationId))
56
+ .get();
57
+
58
+ return row ?? null;
59
+ }
60
+
61
+ /**
62
+ * Look up a thread by its unique binding (assistant_id, source_channel, external_chat_id).
63
+ */
64
+ export function getThreadByBinding(
65
+ assistantId: string,
66
+ sourceChannel: string,
67
+ externalChatId: string,
68
+ ): InboxThread | null {
69
+ const db = getDb();
70
+ const row = db
71
+ .select()
72
+ .from(assistantInboxThreadState)
73
+ .where(
74
+ and(
75
+ eq(assistantInboxThreadState.assistantId, assistantId),
76
+ eq(assistantInboxThreadState.sourceChannel, sourceChannel),
77
+ eq(assistantInboxThreadState.externalChatId, externalChatId),
78
+ ),
79
+ )
80
+ .get();
81
+
82
+ return row ?? null;
83
+ }
84
+
85
+ /**
86
+ * Create or update an inbox thread state row.
87
+ * If a row with the given conversationId exists, updates display fields and updated_at.
88
+ * Otherwise, creates a new row with counters initialized to 0.
89
+ */
90
+ export function upsertThread(params: {
91
+ conversationId: string;
92
+ assistantId?: string;
93
+ sourceChannel: string;
94
+ externalChatId: string;
95
+ externalUserId?: string;
96
+ displayName?: string;
97
+ username?: string;
98
+ }): InboxThread {
99
+ const db = getDb();
100
+ const now = Date.now();
101
+ const assistantId = params.assistantId ?? 'self';
102
+
103
+ const existing = getThread(params.conversationId);
104
+
105
+ if (existing) {
106
+ db.update(assistantInboxThreadState)
107
+ .set({
108
+ displayName: params.displayName ?? existing.displayName,
109
+ username: params.username ?? existing.username,
110
+ externalUserId: params.externalUserId ?? existing.externalUserId,
111
+ updatedAt: now,
112
+ })
113
+ .where(eq(assistantInboxThreadState.conversationId, params.conversationId))
114
+ .run();
115
+
116
+ return {
117
+ ...existing,
118
+ displayName: params.displayName ?? existing.displayName,
119
+ username: params.username ?? existing.username,
120
+ externalUserId: params.externalUserId ?? existing.externalUserId,
121
+ updatedAt: now,
122
+ };
123
+ }
124
+
125
+ const row = {
126
+ conversationId: params.conversationId,
127
+ assistantId,
128
+ sourceChannel: params.sourceChannel,
129
+ externalChatId: params.externalChatId,
130
+ externalUserId: params.externalUserId ?? null,
131
+ displayName: params.displayName ?? null,
132
+ username: params.username ?? null,
133
+ lastInboundAt: null,
134
+ lastOutboundAt: null,
135
+ lastMessageAt: null,
136
+ unreadCount: 0,
137
+ pendingEscalationCount: 0,
138
+ hasPendingEscalation: 0,
139
+ createdAt: now,
140
+ updatedAt: now,
141
+ };
142
+
143
+ db.insert(assistantInboxThreadState).values(row).run();
144
+
145
+ return row;
146
+ }
147
+
148
+ /**
149
+ * Record message activity on a thread.
150
+ * - inbound: updates last_inbound_at, last_message_at, increments unread_count
151
+ * - outbound: updates last_outbound_at, last_message_at, resets unread_count to 0
152
+ */
153
+ export function updateThreadActivity(
154
+ conversationId: string,
155
+ direction: 'inbound' | 'outbound',
156
+ ): void {
157
+ const db = getDb();
158
+ const now = Date.now();
159
+
160
+ if (direction === 'inbound') {
161
+ db.update(assistantInboxThreadState)
162
+ .set({
163
+ lastInboundAt: now,
164
+ lastMessageAt: now,
165
+ unreadCount: sql`${assistantInboxThreadState.unreadCount} + 1`,
166
+ updatedAt: now,
167
+ })
168
+ .where(eq(assistantInboxThreadState.conversationId, conversationId))
169
+ .run();
170
+ } else {
171
+ db.update(assistantInboxThreadState)
172
+ .set({
173
+ lastOutboundAt: now,
174
+ lastMessageAt: now,
175
+ unreadCount: 0,
176
+ updatedAt: now,
177
+ })
178
+ .where(eq(assistantInboxThreadState.conversationId, conversationId))
179
+ .run();
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Mark a thread as read by resetting its unread count.
185
+ */
186
+ export function markThreadRead(conversationId: string): void {
187
+ const db = getDb();
188
+ const now = Date.now();
189
+
190
+ db.update(assistantInboxThreadState)
191
+ .set({
192
+ unreadCount: 0,
193
+ updatedAt: now,
194
+ })
195
+ .where(eq(assistantInboxThreadState.conversationId, conversationId))
196
+ .run();
197
+ }
198
+
199
+ /**
200
+ * Update the escalation state for a thread.
201
+ * Sets pending_escalation_count and derives has_pending_escalation from it.
202
+ */
203
+ export function updateEscalationState(
204
+ conversationId: string,
205
+ pendingCount: number,
206
+ ): void {
207
+ const db = getDb();
208
+ const now = Date.now();
209
+
210
+ db.update(assistantInboxThreadState)
211
+ .set({
212
+ pendingEscalationCount: pendingCount,
213
+ hasPendingEscalation: pendingCount > 0 ? 1 : 0,
214
+ updatedAt: now,
215
+ })
216
+ .where(eq(assistantInboxThreadState.conversationId, conversationId))
217
+ .run();
218
+ }