@vellumai/assistant 0.3.5 → 0.3.7

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 (487) hide show
  1. package/README.md +51 -0
  2. package/eslint.config.mjs +31 -0
  3. package/package.json +1 -1
  4. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  5. package/scripts/ipc/generate-swift.ts +18 -2
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +338 -1
  7. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  8. package/src/__tests__/browser-manager.test.ts +1 -0
  9. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  10. package/src/__tests__/call-orchestrator.test.ts +752 -271
  11. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  12. package/src/__tests__/call-recovery.test.ts +3 -0
  13. package/src/__tests__/call-routes-http.test.ts +5 -0
  14. package/src/__tests__/call-store.test.ts +3 -0
  15. package/src/__tests__/channel-approval-routes.test.ts +1260 -85
  16. package/src/__tests__/channel-approval.test.ts +37 -0
  17. package/src/__tests__/channel-approvals.test.ts +4 -65
  18. package/src/__tests__/channel-guardian.test.ts +556 -0
  19. package/src/__tests__/channel-readiness-service.test.ts +74 -7
  20. package/src/__tests__/checker.test.ts +14 -7
  21. package/src/__tests__/clarification-resolver.test.ts +44 -24
  22. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  23. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  24. package/src/__tests__/config-schema.test.ts +12 -7
  25. package/src/__tests__/context-window-manager.test.ts +30 -2
  26. package/src/__tests__/contradiction-checker.test.ts +20 -5
  27. package/src/__tests__/credential-security-invariants.test.ts +6 -2
  28. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  29. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  30. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  31. package/src/__tests__/guardian-action-store.test.ts +123 -0
  32. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  33. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  34. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  35. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  36. package/src/__tests__/handlers-twilio-config.test.ts +126 -0
  37. package/src/__tests__/intent-routing.test.ts +2 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +228 -1
  39. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  40. package/src/__tests__/model-intents.test.ts +96 -0
  41. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  42. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  43. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  44. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  45. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  46. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  47. package/src/__tests__/qdrant-manager.test.ts +27 -20
  48. package/src/__tests__/relay-server.test.ts +779 -40
  49. package/src/__tests__/run-orchestrator-assistant-events.test.ts +2 -0
  50. package/src/__tests__/run-orchestrator.test.ts +20 -4
  51. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  52. package/src/__tests__/runtime-runs.test.ts +16 -0
  53. package/src/__tests__/schedule-store.test.ts +18 -4
  54. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  55. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  56. package/src/__tests__/session-agent-loop.test.ts +857 -0
  57. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  58. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  59. package/src/__tests__/session-profile-injection.test.ts +6 -0
  60. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  61. package/src/__tests__/session-queue.test.ts +6 -0
  62. package/src/__tests__/session-runtime-assembly.test.ts +237 -13
  63. package/src/__tests__/session-slash-known.test.ts +6 -0
  64. package/src/__tests__/session-slash-queue.test.ts +6 -0
  65. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  66. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  67. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  68. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  69. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  70. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  71. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  72. package/src/__tests__/skills.test.ts +2 -0
  73. package/src/__tests__/sms-messaging-provider.test.ts +2 -1
  74. package/src/__tests__/starter-task-flow.test.ts +2 -0
  75. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  76. package/src/__tests__/system-prompt.test.ts +2 -0
  77. package/src/__tests__/task-management-tools.test.ts +2 -2
  78. package/src/__tests__/task-runner.test.ts +14 -4
  79. package/src/__tests__/terminal-tools.test.ts +25 -19
  80. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  81. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  82. package/src/__tests__/tool-executor.test.ts +23 -24
  83. package/src/__tests__/trust-store.test.ts +3 -3
  84. package/src/__tests__/twilio-rest.test.ts +29 -0
  85. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  86. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  87. package/src/__tests__/twilio-routes.test.ts +141 -21
  88. package/src/__tests__/user-reference.test.ts +2 -0
  89. package/src/__tests__/voice-quality.test.ts +222 -0
  90. package/src/__tests__/web-search.test.ts +45 -29
  91. package/src/agent/loop.ts +1 -1
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  93. package/src/amazon/client.ts +1418 -0
  94. package/src/amazon/request-extractor.ts +135 -0
  95. package/src/amazon/session.ts +109 -0
  96. package/src/autonomy/autonomy-store.ts +5 -5
  97. package/src/browser-extension-relay/client.ts +124 -0
  98. package/src/browser-extension-relay/protocol.ts +63 -0
  99. package/src/browser-extension-relay/server.ts +177 -0
  100. package/src/bundler/app-bundler.ts +3 -3
  101. package/src/bundler/bundle-signer.ts +1 -1
  102. package/src/bundler/signature-verifier.ts +1 -1
  103. package/src/calls/call-conversation-messages.ts +33 -0
  104. package/src/calls/call-domain.ts +106 -5
  105. package/src/calls/call-orchestrator.ts +252 -54
  106. package/src/calls/call-pointer-messages.ts +53 -0
  107. package/src/calls/call-recovery.ts +3 -8
  108. package/src/calls/call-store.ts +69 -87
  109. package/src/calls/elevenlabs-config.ts +3 -2
  110. package/src/calls/guardian-action-sweep.ts +105 -0
  111. package/src/calls/guardian-dispatch.ts +203 -0
  112. package/src/calls/guardian-question-copy.ts +133 -0
  113. package/src/calls/relay-server.ts +466 -8
  114. package/src/calls/speaker-identification.ts +1 -1
  115. package/src/calls/twilio-config.ts +7 -5
  116. package/src/calls/twilio-provider.ts +6 -4
  117. package/src/calls/twilio-rest.ts +40 -15
  118. package/src/calls/twilio-routes.ts +60 -45
  119. package/src/calls/types.ts +3 -1
  120. package/src/channels/types.ts +25 -0
  121. package/src/cli/amazon.ts +815 -0
  122. package/src/cli/config-commands.ts +2 -2
  123. package/src/cli/core-commands.ts +4 -3
  124. package/src/cli/influencer.ts +244 -0
  125. package/src/cli/map.ts +89 -6
  126. package/src/cli.ts +1 -1
  127. package/src/config/agent-schema.ts +171 -0
  128. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  129. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  130. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  131. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  132. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  133. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  134. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  135. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  136. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  137. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  138. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  139. package/src/config/bundled-skills/media-processing/SKILL.md +72 -95
  140. package/src/config/bundled-skills/media-processing/TOOLS.json +57 -147
  141. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  142. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  143. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  144. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  145. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  146. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  147. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  148. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +7 -9
  149. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  150. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +88 -253
  151. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +22 -153
  152. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  153. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +28 -51
  154. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +35 -270
  155. package/src/config/bundled-skills/messaging/SKILL.md +12 -2
  156. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  157. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  158. package/src/config/bundled-skills/phone-calls/SKILL.md +86 -21
  159. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  160. package/src/config/bundled-tool-registry.ts +310 -0
  161. package/src/config/calls-schema.ts +181 -0
  162. package/src/config/core-schema.ts +309 -0
  163. package/src/config/defaults.ts +27 -3
  164. package/src/config/env-registry.ts +169 -0
  165. package/src/config/env.ts +175 -0
  166. package/src/config/loader.ts +6 -6
  167. package/src/config/memory-schema.ts +528 -0
  168. package/src/config/sandbox-schema.ts +55 -0
  169. package/src/config/schema.ts +157 -1138
  170. package/src/config/skill-state.ts +1 -1
  171. package/src/config/skills-schema.ts +32 -0
  172. package/src/config/skills.ts +35 -24
  173. package/src/config/system-prompt.ts +107 -56
  174. package/src/config/templates/SOUL.md +1 -1
  175. package/src/config/types.ts +1 -0
  176. package/src/config/user-reference.ts +4 -9
  177. package/src/config/vellum-skills/catalog.json +0 -7
  178. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  179. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +1 -0
  180. package/src/config/vellum-skills/sms-setup/SKILL.md +112 -14
  181. package/src/context/window-manager.ts +27 -7
  182. package/src/daemon/approval-generators.ts +186 -0
  183. package/src/daemon/approved-devices-store.ts +140 -0
  184. package/src/daemon/assistant-attachments.ts +1 -1
  185. package/src/daemon/classifier.ts +35 -32
  186. package/src/daemon/config-watcher.ts +1 -1
  187. package/src/daemon/daemon-control.ts +254 -0
  188. package/src/daemon/handlers/apps.ts +2 -3
  189. package/src/daemon/handlers/config-channels.ts +158 -0
  190. package/src/daemon/handlers/config-inbox.ts +540 -0
  191. package/src/daemon/handlers/config-ingress.ts +231 -0
  192. package/src/daemon/handlers/config-integrations.ts +258 -0
  193. package/src/daemon/handlers/config-model.ts +143 -0
  194. package/src/daemon/handlers/config-parental.ts +163 -0
  195. package/src/daemon/handlers/config-scheduling.ts +172 -0
  196. package/src/daemon/handlers/config-slack.ts +92 -0
  197. package/src/daemon/handlers/config-telegram.ts +301 -0
  198. package/src/daemon/handlers/config-tools.ts +177 -0
  199. package/src/daemon/handlers/config-trust.ts +104 -0
  200. package/src/daemon/handlers/config-twilio.ts +1080 -0
  201. package/src/daemon/handlers/config.ts +53 -2463
  202. package/src/daemon/handlers/diagnostics.ts +1 -1
  203. package/src/daemon/handlers/dictation.ts +4 -6
  204. package/src/daemon/handlers/documents.ts +18 -32
  205. package/src/daemon/handlers/index.ts +9 -0
  206. package/src/daemon/handlers/misc.ts +3 -5
  207. package/src/daemon/handlers/pairing.ts +98 -0
  208. package/src/daemon/handlers/sessions.ts +74 -5
  209. package/src/daemon/handlers/shared.ts +3 -1
  210. package/src/daemon/handlers/skills.ts +1 -1
  211. package/src/daemon/handlers/twitter-auth.ts +2 -0
  212. package/src/daemon/handlers/work-items.ts +2 -2
  213. package/src/daemon/handlers/workspace-files.ts +4 -3
  214. package/src/daemon/install-cli-launchers.ts +113 -0
  215. package/src/daemon/ipc-contract/apps.ts +356 -0
  216. package/src/daemon/ipc-contract/browser.ts +74 -0
  217. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  218. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  219. package/src/daemon/ipc-contract/documents.ts +74 -0
  220. package/src/daemon/ipc-contract/inbox.ts +209 -0
  221. package/src/daemon/ipc-contract/integrations.ts +284 -0
  222. package/src/daemon/ipc-contract/memory.ts +48 -0
  223. package/src/daemon/ipc-contract/messages.ts +211 -0
  224. package/src/daemon/ipc-contract/pairing.ts +45 -0
  225. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  226. package/src/daemon/ipc-contract/schedules.ts +97 -0
  227. package/src/daemon/ipc-contract/sessions.ts +321 -0
  228. package/src/daemon/ipc-contract/shared.ts +42 -0
  229. package/src/daemon/ipc-contract/skills.ts +120 -0
  230. package/src/daemon/ipc-contract/subagents.ts +58 -0
  231. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  232. package/src/daemon/ipc-contract/trust.ts +60 -0
  233. package/src/daemon/ipc-contract/work-items.ts +225 -0
  234. package/src/daemon/ipc-contract/workspace.ts +113 -0
  235. package/src/daemon/ipc-contract-inventory.json +62 -0
  236. package/src/daemon/ipc-contract-inventory.ts +55 -29
  237. package/src/daemon/ipc-contract.ts +227 -2527
  238. package/src/daemon/ipc-protocol.ts +1 -1
  239. package/src/daemon/ipc-validate.ts +7 -0
  240. package/src/daemon/lifecycle.ts +97 -379
  241. package/src/daemon/pairing-store.ts +177 -0
  242. package/src/daemon/providers-setup.ts +43 -0
  243. package/src/daemon/ride-shotgun-handler.ts +67 -2
  244. package/src/daemon/server.ts +60 -44
  245. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  246. package/src/daemon/session-agent-loop.ts +113 -275
  247. package/src/daemon/session-dynamic-profile.ts +1 -1
  248. package/src/daemon/session-history.ts +1 -1
  249. package/src/daemon/session-media-retry.ts +1 -1
  250. package/src/daemon/session-messaging.ts +37 -2
  251. package/src/daemon/session-notifiers.ts +5 -25
  252. package/src/daemon/session-process.ts +99 -59
  253. package/src/daemon/session-queue-manager.ts +98 -4
  254. package/src/daemon/session-runtime-assembly.ts +149 -15
  255. package/src/daemon/session-surfaces.ts +26 -4
  256. package/src/daemon/session-tool-setup.ts +28 -30
  257. package/src/daemon/session-workspace.ts +1 -1
  258. package/src/daemon/session.ts +24 -1
  259. package/src/daemon/shutdown-handlers.ts +122 -0
  260. package/src/daemon/trace-emitter.ts +1 -1
  261. package/src/daemon/watch-handler.ts +36 -33
  262. package/src/doordash/cart-queries.ts +787 -0
  263. package/src/doordash/client.ts +144 -127
  264. package/src/doordash/order-queries.ts +85 -0
  265. package/src/doordash/queries.ts +10 -1308
  266. package/src/doordash/search-queries.ts +203 -0
  267. package/src/doordash/session.ts +3 -2
  268. package/src/doordash/store-queries.ts +246 -0
  269. package/src/doordash/types.ts +367 -0
  270. package/src/email/providers/agentmail.ts +2 -1
  271. package/src/email/providers/index.ts +3 -2
  272. package/src/email/service.ts +3 -2
  273. package/src/errors.ts +43 -0
  274. package/src/home-base/prebuilt/seed.ts +1 -1
  275. package/src/hooks/cli.ts +6 -5
  276. package/src/hooks/config.ts +6 -8
  277. package/src/hooks/discovery.ts +6 -5
  278. package/src/hooks/manager.ts +4 -3
  279. package/src/hooks/runner.ts +2 -2
  280. package/src/hooks/templates.ts +5 -5
  281. package/src/inbound/public-ingress-urls.ts +3 -1
  282. package/src/index.ts +4 -2
  283. package/src/influencer/client.ts +1104 -0
  284. package/src/instrument.ts +4 -3
  285. package/src/logfire.ts +4 -3
  286. package/src/memory/admin.ts +25 -35
  287. package/src/memory/attachments-store.ts +4 -7
  288. package/src/memory/channel-delivery-store.ts +30 -1
  289. package/src/memory/channel-guardian-store.ts +200 -1
  290. package/src/memory/clarification-resolver.ts +37 -33
  291. package/src/memory/conflict-store.ts +67 -61
  292. package/src/memory/contradiction-checker.ts +141 -117
  293. package/src/memory/conversation-store.ts +335 -51
  294. package/src/memory/db-connection.ts +27 -4
  295. package/src/memory/db-init.ts +121 -4
  296. package/src/memory/db.ts +14 -1
  297. package/src/memory/embedding-backend.ts +27 -5
  298. package/src/memory/embedding-ollama.ts +2 -1
  299. package/src/memory/entity-extractor.ts +38 -35
  300. package/src/memory/guardian-action-store.ts +430 -0
  301. package/src/memory/inbox-escalation-projection.ts +59 -0
  302. package/src/memory/inbox-thread-store.ts +218 -0
  303. package/src/memory/ingress-invite-store.ts +338 -0
  304. package/src/memory/ingress-member-store.ts +350 -0
  305. package/src/memory/items-extractor.ts +91 -97
  306. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  307. package/src/memory/job-handlers/media-processing.ts +11 -42
  308. package/src/memory/job-handlers/summarization.ts +32 -26
  309. package/src/memory/job-utils.ts +3 -10
  310. package/src/memory/jobs-store.ts +6 -9
  311. package/src/memory/jobs-worker.ts +51 -36
  312. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  313. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  314. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  315. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  316. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  317. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  318. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  319. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  320. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  321. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  322. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  323. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  324. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  325. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  326. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  327. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  328. package/src/memory/migrations/017-memory-items-indexes.ts +12 -0
  329. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  330. package/src/memory/migrations/index.ts +24 -0
  331. package/src/memory/migrations/registry.ts +79 -0
  332. package/src/memory/migrations/validate-migration-state.ts +69 -0
  333. package/src/memory/qdrant-manager.ts +49 -8
  334. package/src/memory/query-builder.ts +1 -1
  335. package/src/memory/raw-query.ts +119 -0
  336. package/src/memory/recall-cache.ts +4 -1
  337. package/src/memory/retriever.ts +163 -47
  338. package/src/memory/schema-migration.ts +25 -984
  339. package/src/memory/schema.ts +130 -7
  340. package/src/memory/search/entity.ts +10 -19
  341. package/src/memory/search/lexical.ts +81 -52
  342. package/src/memory/search/ranking.ts +21 -22
  343. package/src/memory/search/semantic.ts +157 -19
  344. package/src/memory/shared-app-links-store.ts +4 -5
  345. package/src/memory/validation.ts +19 -0
  346. package/src/messaging/draft-store.ts +5 -6
  347. package/src/messaging/providers/sms/adapter.ts +3 -6
  348. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  349. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  350. package/src/messaging/providers/whatsapp/client.ts +67 -0
  351. package/src/messaging/style-analyzer.ts +5 -4
  352. package/src/messaging/thread-summarizer.ts +61 -69
  353. package/src/messaging/triage-engine.ts +62 -71
  354. package/src/migrations/config-merge.ts +53 -0
  355. package/src/migrations/data-layout.ts +68 -0
  356. package/src/migrations/data-merge.ts +33 -0
  357. package/src/migrations/hooks-merge.ts +90 -0
  358. package/src/migrations/index.ts +6 -0
  359. package/src/migrations/log.ts +23 -0
  360. package/src/migrations/skills-merge.ts +33 -0
  361. package/src/migrations/workspace-layout.ts +79 -0
  362. package/src/permissions/checker.ts +126 -11
  363. package/src/permissions/prompter.ts +14 -0
  364. package/src/permissions/shell-identity.ts +31 -1
  365. package/src/permissions/trust-store.ts +21 -1
  366. package/src/providers/anthropic/client.ts +4 -4
  367. package/src/providers/failover.ts +2 -2
  368. package/src/providers/model-intents.ts +70 -0
  369. package/src/providers/ollama/client.ts +2 -1
  370. package/src/providers/provider-send-message.ts +176 -0
  371. package/src/providers/registry.ts +71 -30
  372. package/src/providers/retry.ts +35 -1
  373. package/src/providers/types.ts +12 -1
  374. package/src/runtime/approval-conversation-turn.ts +97 -0
  375. package/src/runtime/approval-message-composer.ts +115 -5
  376. package/src/runtime/assistant-event-hub.ts +3 -1
  377. package/src/runtime/channel-approval-parser.ts +36 -2
  378. package/src/runtime/channel-approvals.ts +0 -21
  379. package/src/runtime/channel-guardian-service.ts +48 -7
  380. package/src/runtime/channel-readiness-service.ts +160 -34
  381. package/src/runtime/channel-readiness-types.ts +10 -4
  382. package/src/runtime/channel-retry-sweep.ts +184 -0
  383. package/src/runtime/guardian-context-resolver.ts +108 -0
  384. package/src/runtime/http-server.ts +289 -745
  385. package/src/runtime/http-types.ts +56 -3
  386. package/src/runtime/middleware/auth.ts +116 -0
  387. package/src/runtime/middleware/error-handler.ts +33 -0
  388. package/src/runtime/middleware/twilio-validation.ts +127 -0
  389. package/src/runtime/routes/app-routes.ts +1 -1
  390. package/src/runtime/routes/call-routes.ts +49 -6
  391. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  392. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  393. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  394. package/src/runtime/routes/channel-route-shared.ts +144 -0
  395. package/src/runtime/routes/channel-routes.ts +32 -1634
  396. package/src/runtime/routes/conversation-routes.ts +50 -7
  397. package/src/runtime/routes/events-routes.ts +2 -2
  398. package/src/runtime/routes/identity-routes.ts +126 -0
  399. package/src/runtime/routes/pairing-routes.ts +144 -0
  400. package/src/runtime/routes/run-routes.ts +15 -1
  401. package/src/runtime/run-orchestrator.ts +52 -34
  402. package/src/schedule/schedule-store.ts +36 -32
  403. package/src/schedule/scheduler.ts +3 -3
  404. package/src/security/encrypted-store.ts +5 -7
  405. package/src/security/oauth2.ts +45 -15
  406. package/src/security/parental-control-store.ts +183 -0
  407. package/src/security/secret-allowlist.ts +4 -3
  408. package/src/security/secret-scanner.ts +5 -5
  409. package/src/security/secure-keys.ts +1 -1
  410. package/src/security/token-manager.ts +3 -2
  411. package/src/services/vercel-deploy.ts +6 -2
  412. package/src/skills/tool-manifest.ts +3 -3
  413. package/src/skills/vellum-catalog-remote.ts +75 -16
  414. package/src/slack/slack-webhook.ts +2 -1
  415. package/src/swarm/orchestrator.ts +92 -1
  416. package/src/swarm/router-planner.ts +6 -9
  417. package/src/swarm/worker-prompts.ts +9 -12
  418. package/src/tasks/task-compiler.ts +19 -28
  419. package/src/tasks/task-runner.ts +1 -1
  420. package/src/tools/assets/search.ts +15 -14
  421. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  422. package/src/tools/browser/auto-navigate.ts +1 -0
  423. package/src/tools/browser/browser-execution.ts +13 -1
  424. package/src/tools/browser/browser-manager.ts +119 -4
  425. package/src/tools/browser/network-recorder.ts +5 -0
  426. package/src/tools/credentials/broker.ts +11 -2
  427. package/src/tools/credentials/metadata-store.ts +18 -14
  428. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  429. package/src/tools/credentials/vault.ts +49 -23
  430. package/src/tools/executor.ts +80 -18
  431. package/src/tools/host-terminal/cli-discover.ts +1 -1
  432. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  433. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  434. package/src/tools/network/script-proxy/server.ts +1 -1
  435. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  436. package/src/tools/network/web-fetch.ts +18 -2
  437. package/src/tools/network/web-search.ts +7 -3
  438. package/src/tools/reminder/reminder-store.ts +14 -15
  439. package/src/tools/schedule/create.ts +1 -0
  440. package/src/tools/schedule/list.ts +2 -1
  441. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  442. package/src/tools/skills/skill-script-runner.ts +24 -9
  443. package/src/tools/skills/skill-tool-factory.ts +1 -0
  444. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  445. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  446. package/src/tools/terminal/parser.ts +50 -0
  447. package/src/tools/watcher/delete.ts +6 -0
  448. package/src/tools/weather/service.ts +1 -1
  449. package/src/twitter/client.ts +190 -24
  450. package/src/twitter/session.ts +4 -3
  451. package/src/util/clipboard.ts +1 -1
  452. package/src/util/errors.ts +65 -8
  453. package/src/util/fs.ts +40 -0
  454. package/src/util/json.ts +10 -0
  455. package/src/util/log-redact.ts +189 -0
  456. package/src/util/logger.ts +25 -18
  457. package/src/util/object.ts +3 -0
  458. package/src/util/platform.ts +72 -365
  459. package/src/util/pricing.ts +1 -1
  460. package/src/util/promise-guard.ts +1 -1
  461. package/src/util/retry.ts +19 -0
  462. package/src/util/row-mapper.ts +79 -0
  463. package/src/util/silently.ts +21 -0
  464. package/src/watcher/engine.ts +5 -1
  465. package/src/watcher/provider-types.ts +20 -0
  466. package/src/watcher/providers/github.ts +156 -0
  467. package/src/watcher/providers/gmail.ts +1 -0
  468. package/src/watcher/providers/google-calendar.ts +1 -0
  469. package/src/watcher/providers/linear.ts +460 -0
  470. package/src/watcher/providers/slack.ts +1 -0
  471. package/src/work-items/work-item-runner.ts +1 -1
  472. package/src/workspace/git-service.ts +1 -1
  473. package/src/workspace/provider-commit-message-generator.ts +51 -22
  474. package/src/__tests__/call-bridge.test.ts +0 -517
  475. package/src/__tests__/session-process-bridge.test.ts +0 -244
  476. package/src/calls/call-bridge.ts +0 -168
  477. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +0 -137
  478. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
  479. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
  480. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
  481. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
  482. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
  483. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
  484. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
  485. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
  486. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
  487. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -1,14 +1,93 @@
1
- import { eq, desc, asc, and, count, sql, inArray } from 'drizzle-orm';
1
+ import { eq, desc, asc, and, count, sql, inArray, or, isNull } from 'drizzle-orm';
2
2
  import { v4 as uuid } from 'uuid';
3
- import { getDb } from './db.js';
3
+ import { z } from 'zod';
4
+ import { getDb, rawGet, rawExec } from './db.js';
4
5
  import { conversations, messages, toolInvocations, messageRuns, channelInboundEvents, memoryItemSources, memoryItems, memoryEmbeddings, memoryItemEntities, memorySegments, messageAttachments, llmRequestLogs } from './schema.js';
5
6
  import { getConfig } from '../config/loader.js';
6
7
  import { indexMessageNow } from './indexer.js';
8
+ import { parseChannelId } from '../channels/types.js';
9
+ import type { ChannelId } from '../channels/types.js';
10
+ import { isChannelId, CHANNEL_IDS } from '../channels/types.js';
7
11
  import { getLogger } from '../util/logger.js';
8
12
  import { deleteOrphanAttachments } from './attachments-store.js';
13
+ import { createRowMapper } from '../util/row-mapper.js';
9
14
 
10
15
  const log = getLogger('conversation-store');
11
16
 
17
+ // ── Message metadata Zod schema ──────────────────────────────────────
18
+ // Validates the JSON stored in messages.metadata. Known fields are typed;
19
+ // extra keys are allowed via passthrough so callers can attach ad-hoc data.
20
+
21
+ const channelIdSchema = z.enum(CHANNEL_IDS);
22
+
23
+ const subagentNotificationSchema = z.object({
24
+ subagentId: z.string(),
25
+ label: z.string(),
26
+ status: z.enum(['completed', 'failed', 'aborted']),
27
+ error: z.string().optional(),
28
+ conversationId: z.string().optional(),
29
+ });
30
+
31
+ export const messageMetadataSchema = z.object({
32
+ userMessageChannel: channelIdSchema.optional(),
33
+ assistantMessageChannel: channelIdSchema.optional(),
34
+ subagentNotification: subagentNotificationSchema.optional(),
35
+ }).passthrough();
36
+
37
+ export type MessageMetadata = z.infer<typeof messageMetadataSchema>;
38
+
39
+ export interface ConversationRow {
40
+ id: string;
41
+ title: string | null;
42
+ createdAt: number;
43
+ updatedAt: number;
44
+ totalInputTokens: number;
45
+ totalOutputTokens: number;
46
+ totalEstimatedCost: number;
47
+ contextSummary: string | null;
48
+ contextCompactedMessageCount: number;
49
+ contextCompactedAt: number | null;
50
+ threadType: string;
51
+ source: string;
52
+ memoryScopeId: string;
53
+ originChannel: string | null;
54
+ }
55
+
56
+ const parseConversation = createRowMapper<typeof conversations.$inferSelect, ConversationRow>({
57
+ id: 'id',
58
+ title: 'title',
59
+ createdAt: 'createdAt',
60
+ updatedAt: 'updatedAt',
61
+ totalInputTokens: 'totalInputTokens',
62
+ totalOutputTokens: 'totalOutputTokens',
63
+ totalEstimatedCost: 'totalEstimatedCost',
64
+ contextSummary: 'contextSummary',
65
+ contextCompactedMessageCount: 'contextCompactedMessageCount',
66
+ contextCompactedAt: 'contextCompactedAt',
67
+ threadType: 'threadType',
68
+ source: 'source',
69
+ memoryScopeId: 'memoryScopeId',
70
+ originChannel: 'originChannel',
71
+ });
72
+
73
+ export interface MessageRow {
74
+ id: string;
75
+ conversationId: string;
76
+ role: string;
77
+ content: string;
78
+ createdAt: number;
79
+ metadata: string | null;
80
+ }
81
+
82
+ const parseMessage = createRowMapper<typeof messages.$inferSelect, MessageRow>({
83
+ id: 'id',
84
+ conversationId: 'conversationId',
85
+ role: 'role',
86
+ content: 'content',
87
+ createdAt: 'createdAt',
88
+ metadata: 'metadata',
89
+ });
90
+
12
91
  /**
13
92
  * Monotonic timestamp source for message ordering. Two messages saved within
14
93
  * the same millisecond (e.g., tool_results user message + assistant message in
@@ -23,11 +102,12 @@ function monotonicNow(): number {
23
102
  return lastTimestamp;
24
103
  }
25
104
 
26
- export function createConversation(titleOrOpts?: string | { title?: string; threadType?: 'standard' | 'private' | 'background' }) {
105
+ export function createConversation(titleOrOpts?: string | { title?: string; threadType?: 'standard' | 'private' | 'background'; source?: string }) {
27
106
  const db = getDb();
28
107
  const now = Date.now();
29
108
  const opts = typeof titleOrOpts === 'string' ? { title: titleOrOpts } : (titleOrOpts ?? {});
30
109
  const threadType = opts.threadType ?? 'standard';
110
+ const source = opts.source ?? 'user';
31
111
  const id = uuid();
32
112
  const memoryScopeId = threadType === 'private' ? `private:${id}` : 'default';
33
113
  const conversation = {
@@ -42,20 +122,21 @@ export function createConversation(titleOrOpts?: string | { title?: string; thre
42
122
  contextCompactedMessageCount: 0,
43
123
  contextCompactedAt: null as number | null,
44
124
  threadType,
125
+ source,
45
126
  memoryScopeId,
46
127
  };
47
128
  db.insert(conversations).values(conversation).run();
48
129
  return conversation;
49
130
  }
50
131
 
51
- export function getConversation(id: string) {
132
+ export function getConversation(id: string): ConversationRow | null {
52
133
  const db = getDb();
53
- const result = db
134
+ const row = db
54
135
  .select()
55
136
  .from(conversations)
56
137
  .where(eq(conversations.id, id))
57
138
  .get();
58
- return result ?? null;
139
+ return row ? parseConversation(row) : null;
59
140
  }
60
141
 
61
142
  export function getConversationThreadType(conversationId: string): 'standard' | 'private' {
@@ -84,7 +165,7 @@ export function deleteConversation(id: string): void {
84
165
  });
85
166
  }
86
167
 
87
- export function listConversations(limit?: number, includeBackground = false, offset = 0) {
168
+ export function listConversations(limit?: number, includeBackground = false, offset = 0): ConversationRow[] {
88
169
  const db = getDb();
89
170
  const where = includeBackground ? undefined : sql`${conversations.threadType} != 'background'`;
90
171
  const query = db
@@ -94,7 +175,7 @@ export function listConversations(limit?: number, includeBackground = false, off
94
175
  .orderBy(desc(conversations.updatedAt))
95
176
  .limit(limit ?? 100)
96
177
  .offset(offset);
97
- return query.all();
178
+ return query.all().map(parseConversation);
98
179
  }
99
180
 
100
181
  export function countConversations(includeBackground = false): number {
@@ -108,37 +189,74 @@ export function countConversations(includeBackground = false): number {
108
189
  return total;
109
190
  }
110
191
 
111
- export function getLatestConversation() {
192
+ export function getLatestConversation(): ConversationRow | null {
112
193
  const db = getDb();
113
- const result = db
194
+ const row = db
114
195
  .select()
115
196
  .from(conversations)
116
197
  .where(sql`${conversations.threadType} != 'background'`)
117
198
  .orderBy(desc(conversations.updatedAt))
118
199
  .limit(1)
119
200
  .get();
120
- return result ?? null;
201
+ return row ? parseConversation(row) : null;
121
202
  }
122
203
 
123
204
  export function addMessage(conversationId: string, role: string, content: string, metadata?: Record<string, unknown>) {
124
205
  const db = getDb();
125
- const now = monotonicNow();
126
- const message = {
127
- id: uuid(),
128
- conversationId,
129
- role,
130
- content,
131
- createdAt: now,
132
- ...(metadata ? { metadata: JSON.stringify(metadata) } : {}),
133
- };
206
+ const messageId = uuid();
207
+
208
+ if (metadata) {
209
+ const result = messageMetadataSchema.safeParse(metadata);
210
+ if (!result.success) {
211
+ log.warn({ conversationId, messageId, issues: result.error.issues }, 'Invalid message metadata, storing as-is');
212
+ }
213
+ }
214
+
215
+ const metadataStr = metadata ? JSON.stringify(metadata) : undefined;
216
+ const originChannelCandidate =
217
+ metadata && isChannelId(metadata.userMessageChannel)
218
+ ? metadata.userMessageChannel
219
+ : null;
134
220
  // Wrap insert + updatedAt bump in a transaction so they're atomic.
135
- db.transaction((tx) => {
136
- tx.insert(messages).values(message).run();
137
- tx.update(conversations)
138
- .set({ updatedAt: now })
139
- .where(eq(conversations.id, conversationId))
140
- .run();
141
- });
221
+ // Retry on SQLITE_BUSY in case busy_timeout is exhausted under heavy contention.
222
+ // Timestamp is recomputed each attempt so a late retry doesn't persist a stale updatedAt.
223
+ const MAX_RETRIES = 3;
224
+ let now!: number;
225
+ for (let attempt = 0; ; attempt++) {
226
+ now = monotonicNow();
227
+ try {
228
+ const values = {
229
+ id: messageId,
230
+ conversationId,
231
+ role,
232
+ content,
233
+ createdAt: now,
234
+ ...(metadataStr ? { metadata: metadataStr } : {}),
235
+ };
236
+ db.transaction((tx) => {
237
+ tx.insert(messages).values(values).run();
238
+ if (originChannelCandidate) {
239
+ tx.update(conversations)
240
+ .set({ originChannel: originChannelCandidate })
241
+ .where(and(eq(conversations.id, conversationId), isNull(conversations.originChannel)))
242
+ .run();
243
+ }
244
+ tx.update(conversations)
245
+ .set({ updatedAt: now })
246
+ .where(eq(conversations.id, conversationId))
247
+ .run();
248
+ });
249
+ break;
250
+ } catch (err) {
251
+ if (attempt < MAX_RETRIES && (err as { code?: string }).code === 'SQLITE_BUSY') {
252
+ log.warn({ attempt, conversationId }, 'addMessage: SQLITE_BUSY, retrying');
253
+ Bun.sleepSync(50 * (attempt + 1));
254
+ continue;
255
+ }
256
+ throw err;
257
+ }
258
+ }
259
+ const message = { id: messageId, conversationId, role, content, createdAt: now, ...(metadataStr ? { metadata: metadataStr } : {}) };
142
260
 
143
261
  try {
144
262
  const config = getConfig();
@@ -158,14 +276,15 @@ export function addMessage(conversationId: string, role: string, content: string
158
276
  return message;
159
277
  }
160
278
 
161
- export function getMessages(conversationId: string) {
279
+ export function getMessages(conversationId: string): MessageRow[] {
162
280
  const db = getDb();
163
281
  return db
164
282
  .select()
165
283
  .from(messages)
166
284
  .where(eq(messages.conversationId, conversationId))
167
285
  .orderBy(asc(messages.createdAt))
168
- .all();
286
+ .all()
287
+ .map(parseMessage);
169
288
  }
170
289
 
171
290
  export function updateConversationTitle(id: string, title: string): void {
@@ -218,30 +337,27 @@ export function updateConversationContextWindow(
218
337
  * Returns { conversations, messages } counts.
219
338
  */
220
339
  export function clearAll(): { conversations: number; messages: number } {
221
- const db = getDb();
222
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
223
-
224
- const msgCount = (raw.query('SELECT COUNT(*) AS c FROM messages').get() as { c: number }).c;
225
- const convCount = (raw.query('SELECT COUNT(*) AS c FROM conversations').get() as { c: number }).c;
340
+ const msgCount = rawGet<{ c: number }>('SELECT COUNT(*) AS c FROM messages')?.c ?? 0;
341
+ const convCount = rawGet<{ c: number }>('SELECT COUNT(*) AS c FROM conversations')?.c ?? 0;
226
342
 
227
343
  // Delete in dependency order. Cascades handle memory_segments,
228
344
  // memory_item_sources, and tool_invocations, but we explicitly
229
345
  // clear non-cascading memory tables too.
230
- raw.exec('DELETE FROM memory_segment_fts');
231
- raw.exec('DELETE FROM memory_item_sources');
232
- raw.exec('DELETE FROM memory_segments');
233
- raw.exec('DELETE FROM memory_items');
234
- raw.exec('DELETE FROM memory_summaries');
235
- raw.exec('DELETE FROM memory_embeddings');
236
- raw.exec('DELETE FROM memory_jobs');
237
- raw.exec('DELETE FROM memory_checkpoints');
238
- raw.exec('DELETE FROM llm_request_logs');
239
- raw.exec('DELETE FROM llm_usage_events');
240
- raw.exec('DELETE FROM message_attachments');
241
- raw.exec('DELETE FROM attachments');
242
- raw.exec('DELETE FROM tool_invocations');
243
- raw.exec('DELETE FROM messages');
244
- raw.exec('DELETE FROM conversations');
346
+ rawExec('DELETE FROM memory_segment_fts');
347
+ rawExec('DELETE FROM memory_item_sources');
348
+ rawExec('DELETE FROM memory_segments');
349
+ rawExec('DELETE FROM memory_items');
350
+ rawExec('DELETE FROM memory_summaries');
351
+ rawExec('DELETE FROM memory_embeddings');
352
+ rawExec('DELETE FROM memory_jobs');
353
+ rawExec('DELETE FROM memory_checkpoints');
354
+ rawExec('DELETE FROM llm_request_logs');
355
+ rawExec('DELETE FROM llm_usage_events');
356
+ rawExec('DELETE FROM message_attachments');
357
+ rawExec('DELETE FROM attachments');
358
+ rawExec('DELETE FROM tool_invocations');
359
+ rawExec('DELETE FROM messages');
360
+ rawExec('DELETE FROM conversations');
245
361
 
246
362
  return { conversations: convCount, messages: msgCount };
247
363
  }
@@ -309,7 +425,7 @@ export function deleteLastExchange(conversationId: string): number {
309
425
  .where(inArray(messageAttachments.messageId, messageIds))
310
426
  .all()
311
427
  .map((r) => r.attachmentId)
312
- .filter((id): id is string => id !== null)
428
+ .filter((id): id is string => id != null)
313
429
  : [];
314
430
 
315
431
  db.transaction((tx) => {
@@ -401,7 +517,7 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
401
517
  .where(eq(messageAttachments.messageId, messageId))
402
518
  .all()
403
519
  .map((r) => r.attachmentId)
404
- .filter((id): id is string => id !== null);
520
+ .filter((id): id is string => id !== undefined);
405
521
 
406
522
  db.transaction((tx) => {
407
523
  // Collect memory segment IDs linked to this message before cascade.
@@ -480,3 +596,171 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
480
596
 
481
597
  return result;
482
598
  }
599
+
600
+ export interface ConversationSearchResult {
601
+ conversationId: string;
602
+ conversationTitle: string | null;
603
+ conversationUpdatedAt: number;
604
+ matchingMessages: Array<{
605
+ messageId: string;
606
+ role: string;
607
+ /** Plain-text excerpt around the match, truncated to ~200 chars. */
608
+ excerpt: string;
609
+ createdAt: number;
610
+ }>;
611
+ }
612
+
613
+ /**
614
+ * Full-text search across message content using SQL LIKE.
615
+ * Searches message content for the query string and returns matching
616
+ * conversations with their relevant messages, ordered by most recently updated.
617
+ *
618
+ * Messages that contain JSON-encoded content blocks are searched by their
619
+ * raw content string — the query will match inside code blocks, tool call text,
620
+ * and plain text alike. This is intentional: it avoids a full content parse
621
+ * at query time and stays fast even on large databases.
622
+ */
623
+ export function searchConversations(
624
+ query: string,
625
+ opts?: { limit?: number; maxMessagesPerConversation?: number },
626
+ ): ConversationSearchResult[] {
627
+ if (!query.trim()) return [];
628
+
629
+ const db = getDb();
630
+ const limit = opts?.limit ?? 20;
631
+ const maxMsgsPerConv = opts?.maxMessagesPerConversation ?? 3;
632
+ // Escape backslashes first so they don't interfere with subsequent % and _ escaping.
633
+ // With ESCAPE '\\', SQLite treats \\ as a literal backslash, \% as a literal percent,
634
+ // and \_ as a literal underscore — so all three characters must be escaped in that order.
635
+ const pattern = `%${query.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_')}%`;
636
+
637
+ // Find conversations whose title or at least one message content matches the query.
638
+ // leftJoin ensures title-only matches are included even for empty conversations
639
+ // (innerJoin would silently drop them).
640
+ // ESCAPE '\\' is required for SQLite to treat the backslashes in the pattern as
641
+ // escape characters rather than literals; SQLite has no default escape character.
642
+ const matchingConversations = db
643
+ .select({
644
+ id: conversations.id,
645
+ title: conversations.title,
646
+ updatedAt: conversations.updatedAt,
647
+ })
648
+ .from(conversations)
649
+ .leftJoin(messages, eq(messages.conversationId, conversations.id))
650
+ .where(
651
+ and(
652
+ sql`${conversations.threadType} != 'background'`,
653
+ or(
654
+ sql`${messages.content} LIKE ${pattern} ESCAPE '\\'`,
655
+ sql`${conversations.title} LIKE ${pattern} ESCAPE '\\'`,
656
+ ),
657
+ ),
658
+ )
659
+ .groupBy(conversations.id)
660
+ .orderBy(desc(conversations.updatedAt))
661
+ .limit(limit)
662
+ .all();
663
+
664
+ if (matchingConversations.length === 0) return [];
665
+
666
+ const results: ConversationSearchResult[] = [];
667
+
668
+ for (const conv of matchingConversations) {
669
+ // Fetch the top matching messages for this conversation.
670
+ const matchingMsgs = db
671
+ .select({
672
+ id: messages.id,
673
+ role: messages.role,
674
+ content: messages.content,
675
+ createdAt: messages.createdAt,
676
+ })
677
+ .from(messages)
678
+ .where(
679
+ and(
680
+ eq(messages.conversationId, conv.id),
681
+ // ESCAPE '\\' required so backslashes in the pattern act as escape characters.
682
+ sql`${messages.content} LIKE ${pattern} ESCAPE '\\'`,
683
+ ),
684
+ )
685
+ .orderBy(asc(messages.createdAt))
686
+ .limit(maxMsgsPerConv)
687
+ .all();
688
+
689
+ results.push({
690
+ conversationId: conv.id,
691
+ conversationTitle: conv.title,
692
+ conversationUpdatedAt: conv.updatedAt,
693
+ matchingMessages: matchingMsgs.map((m) => ({
694
+ messageId: m.id,
695
+ role: m.role,
696
+ excerpt: buildExcerpt(m.content, query),
697
+ createdAt: m.createdAt,
698
+ })),
699
+ });
700
+ }
701
+
702
+ return results;
703
+ }
704
+
705
+ /**
706
+ * Build a short excerpt from raw message content centered around the first
707
+ * occurrence of `query`. The content may be JSON (content blocks) or plain
708
+ * text; we extract a readable snippet in either case.
709
+ */
710
+ function buildExcerpt(rawContent: string, query: string): string {
711
+ // Try to extract plain text from JSON content blocks first.
712
+ let text = rawContent;
713
+ try {
714
+ const parsed = JSON.parse(rawContent);
715
+ if (Array.isArray(parsed)) {
716
+ const parts: string[] = [];
717
+ for (const block of parsed) {
718
+ if (typeof block === 'object' && block != null) {
719
+ if (block.type === 'text' && typeof block.text === 'string') {
720
+ parts.push(block.text);
721
+ } else if (block.type === 'tool_result') {
722
+ const inner = Array.isArray(block.content) ? block.content : [];
723
+ for (const ib of inner) {
724
+ if (ib?.type === 'text' && typeof ib.text === 'string') parts.push(ib.text);
725
+ }
726
+ }
727
+ }
728
+ }
729
+ if (parts.length > 0) text = parts.join(' ');
730
+ } else if (typeof parsed === 'string') {
731
+ text = parsed;
732
+ }
733
+ } catch {
734
+ // Not JSON — use as-is
735
+ }
736
+
737
+ const WINDOW = 100;
738
+ const lowerText = text.toLowerCase();
739
+ const lowerQuery = query.toLowerCase();
740
+ const idx = lowerText.indexOf(lowerQuery);
741
+ if (idx === -1) {
742
+ // Query matched the raw JSON but not the extracted text — fall back to raw start
743
+ return text.slice(0, WINDOW * 2).replace(/\s+/g, ' ').trim();
744
+ }
745
+ const start = Math.max(0, idx - WINDOW);
746
+ const end = Math.min(text.length, idx + query.length + WINDOW);
747
+ const excerpt = (start > 0 ? '…' : '') + text.slice(start, end).replace(/\s+/g, ' ').trim() + (end < text.length ? '…' : '');
748
+ return excerpt;
749
+ }
750
+
751
+ export function setConversationOriginChannelIfUnset(conversationId: string, channel: ChannelId): void {
752
+ const db = getDb();
753
+ db.update(conversations)
754
+ .set({ originChannel: channel })
755
+ .where(and(eq(conversations.id, conversationId), isNull(conversations.originChannel)))
756
+ .run();
757
+ }
758
+
759
+ export function getConversationOriginChannel(conversationId: string): ChannelId | null {
760
+ const db = getDb();
761
+ const row = db.select({ originChannel: conversations.originChannel })
762
+ .from(conversations)
763
+ .where(eq(conversations.id, conversationId))
764
+ .get();
765
+ return parseChannelId(row?.originChannel) ?? null;
766
+ }
@@ -3,26 +3,49 @@ import { drizzle } from 'drizzle-orm/bun-sqlite';
3
3
  import * as schema from './schema.js';
4
4
  import { getDbPath, ensureDataDir, migrateToDataLayout, migrateToWorkspaceLayout } from '../util/platform.js';
5
5
 
6
- let db: ReturnType<typeof drizzle<typeof schema>> | null = null;
6
+ export type DrizzleDb = ReturnType<typeof drizzle<typeof schema>>;
7
7
 
8
- export function getDb() {
8
+ let db: DrizzleDb | null = null;
9
+
10
+ export function getDb(): DrizzleDb {
9
11
  if (!db) {
10
12
  migrateToDataLayout();
11
13
  migrateToWorkspaceLayout();
12
14
  ensureDataDir();
13
15
  const sqlite = new Database(getDbPath());
14
16
  sqlite.exec('PRAGMA journal_mode=WAL');
17
+ sqlite.exec('PRAGMA synchronous=FULL');
18
+ sqlite.exec('PRAGMA busy_timeout=5000');
15
19
  sqlite.exec('PRAGMA foreign_keys = ON');
20
+ sqlite.exec('PRAGMA cache_size=-256000');
21
+ sqlite.exec('PRAGMA temp_store=MEMORY');
16
22
  db = drizzle(sqlite, { schema });
17
23
  }
18
24
  return db;
19
25
  }
20
26
 
27
+ /**
28
+ * Get the underlying bun:sqlite Database from the global Drizzle instance.
29
+ *
30
+ * Use this instead of the raw cast `(db as unknown as { $client: Database }).$client`.
31
+ * See raw-query.ts for typed query helpers and guidelines on when raw SQL is appropriate.
32
+ */
33
+ export function getSqlite(): Database {
34
+ return getSqliteFrom(getDb());
35
+ }
36
+
37
+ /**
38
+ * Extract the underlying bun:sqlite Database from any Drizzle instance.
39
+ * Useful in migrations and tests that receive the Drizzle instance as a parameter.
40
+ */
41
+ export function getSqliteFrom(drizzleDb: DrizzleDb): Database {
42
+ return (drizzleDb as unknown as { $client: Database }).$client;
43
+ }
44
+
21
45
  /** Reset the db singleton. Used by tests to ensure isolation between test files. */
22
46
  export function resetDb(): void {
23
47
  if (db) {
24
- const raw = (db as unknown as { $client: Database }).$client;
25
- raw.close();
48
+ getSqliteFrom(db).close();
26
49
  db = null;
27
50
  }
28
51
  }