@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,984 +1,25 @@
1
- import { Database } from 'bun:sqlite';
2
- import { drizzle } from 'drizzle-orm/bun-sqlite';
3
- import { computeMemoryFingerprint } from './fingerprint.js';
4
- import type * as schema from './schema.js';
5
- import { getLogger } from '../util/logger.js';
6
-
7
- const log = getLogger('memory-db');
8
-
9
- type Db = ReturnType<typeof drizzle<typeof schema>>;
10
-
11
- /**
12
- * One-shot migration: reconcile old deferral history into the new `deferrals` column.
13
- *
14
- * Before the `deferrals` column was added, `deferMemoryJob` incremented `attempts`.
15
- * After the column is added with DEFAULT 0, those legacy jobs still carry the old
16
- * attempt count (which was really a deferral count) while `deferrals` is 0. This
17
- * moves the attempt count into `deferrals` and resets `attempts` to 0.
18
- *
19
- * This migration MUST run only once. On subsequent startups, post-migration jobs
20
- * that genuinely failed via `failMemoryJob` (attempts > 0, deferrals = 0, non-null
21
- * last_error) must NOT be touched — resetting their attempts would let them bypass
22
- * the configured maxAttempts budget across restarts.
23
- *
24
- * We use a `memory_checkpoints` row to ensure the migration runs exactly once.
25
- */
26
- export function migrateJobDeferrals(database: Db): void {
27
- const raw = (database as unknown as { $client: Database }).$client;
28
- const checkpoint = raw.query(
29
- `SELECT 1 FROM memory_checkpoints WHERE key = 'migration_job_deferrals'`
30
- ).get();
31
- if (checkpoint) return;
32
-
33
- try {
34
- raw.exec(/*sql*/ `
35
- BEGIN;
36
- UPDATE memory_jobs
37
- SET deferrals = attempts,
38
- attempts = 0,
39
- last_error = NULL,
40
- updated_at = ${Date.now()}
41
- WHERE status = 'pending'
42
- AND attempts > 0
43
- AND deferrals = 0
44
- AND type IN ('embed_segment', 'embed_item', 'embed_summary');
45
- INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at)
46
- VALUES ('migration_job_deferrals', '1', ${Date.now()});
47
- COMMIT;
48
- `);
49
- } catch (e) {
50
- try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
51
- throw e;
52
- }
53
- }
54
-
55
- /**
56
- * Migrate existing tool_invocations table to add FK constraint with ON DELETE CASCADE.
57
- * SQLite doesn't support ALTER TABLE ADD CONSTRAINT, so we rebuild the table.
58
- * This is idempotent: it checks whether the FK already exists before migrating.
59
- */
60
- export function migrateToolInvocationsFk(database: Db): void {
61
- const raw = (database as unknown as { $client: Database }).$client;
62
- const row = raw.query(`SELECT sql FROM sqlite_master WHERE type='table' AND name='tool_invocations'`).get() as { sql: string } | null;
63
- if (!row) return; // table doesn't exist yet (will be created above)
64
-
65
- // If the DDL already contains REFERENCES, the FK is in place
66
- if (row.sql.includes('REFERENCES')) return;
67
-
68
- raw.exec('PRAGMA foreign_keys = OFF');
69
- try {
70
- raw.exec(/*sql*/ `
71
- BEGIN;
72
- CREATE TABLE tool_invocations_new (
73
- id TEXT PRIMARY KEY,
74
- conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
75
- tool_name TEXT NOT NULL,
76
- input TEXT NOT NULL,
77
- result TEXT NOT NULL,
78
- decision TEXT NOT NULL,
79
- risk_level TEXT NOT NULL,
80
- duration_ms INTEGER NOT NULL,
81
- created_at INTEGER NOT NULL
82
- );
83
- INSERT INTO tool_invocations_new SELECT t.* FROM tool_invocations t
84
- WHERE EXISTS (SELECT 1 FROM conversations c WHERE c.id = t.conversation_id);
85
- DROP TABLE tool_invocations;
86
- ALTER TABLE tool_invocations_new RENAME TO tool_invocations;
87
- COMMIT;
88
- `);
89
- } catch (e) {
90
- try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
91
- throw e;
92
- } finally {
93
- raw.exec('PRAGMA foreign_keys = ON');
94
- }
95
- }
96
-
97
- /**
98
- * Backfill FTS rows for existing memory_segments records when upgrading from a
99
- * version that may not have had trigger-managed FTS.
100
- */
101
- export function migrateMemoryFtsBackfill(database: Db): void {
102
- const raw = (database as unknown as { $client: Database }).$client;
103
- const ftsCountRow = raw.query(`SELECT COUNT(*) AS c FROM memory_segment_fts`).get() as { c: number } | null;
104
- const ftsCount = ftsCountRow?.c ?? 0;
105
- if (ftsCount > 0) return;
106
-
107
- raw.exec(/*sql*/ `
108
- INSERT INTO memory_segment_fts(segment_id, text)
109
- SELECT id, text FROM memory_segments
110
- `);
111
- }
112
-
113
- /**
114
- * One-shot migration: merge duplicate relation edges so uniqueness can be
115
- * enforced on (source_entity_id, target_entity_id, relation).
116
- */
117
- export function migrateMemoryEntityRelationDedup(database: Db): void {
118
- const raw = (database as unknown as { $client: Database }).$client;
119
- const checkpointKey = 'migration_memory_entity_relations_dedup_v1';
120
- const checkpoint = raw.query(
121
- `SELECT 1 FROM memory_checkpoints WHERE key = ?`,
122
- ).get(checkpointKey);
123
- if (checkpoint) return;
124
-
125
- try {
126
- raw.exec('BEGIN');
127
-
128
- raw.exec(/*sql*/ `
129
- CREATE TEMP TABLE memory_entity_relation_merge AS
130
- WITH ranked AS (
131
- SELECT
132
- source_entity_id,
133
- target_entity_id,
134
- relation,
135
- first_seen_at,
136
- last_seen_at,
137
- evidence,
138
- ROW_NUMBER() OVER (
139
- PARTITION BY source_entity_id, target_entity_id, relation
140
- ORDER BY last_seen_at DESC, first_seen_at DESC, id DESC
141
- ) AS rank_latest
142
- FROM memory_entity_relations
143
- )
144
- SELECT
145
- source_entity_id,
146
- target_entity_id,
147
- relation,
148
- MIN(first_seen_at) AS merged_first_seen_at,
149
- MAX(last_seen_at) AS merged_last_seen_at,
150
- MAX(CASE WHEN rank_latest = 1 THEN evidence ELSE NULL END) AS merged_evidence
151
- FROM ranked
152
- GROUP BY source_entity_id, target_entity_id, relation
153
- `);
154
-
155
- raw.exec(/*sql*/ `DELETE FROM memory_entity_relations`);
156
-
157
- raw.exec(/*sql*/ `
158
- INSERT INTO memory_entity_relations (
159
- id,
160
- source_entity_id,
161
- target_entity_id,
162
- relation,
163
- evidence,
164
- first_seen_at,
165
- last_seen_at
166
- )
167
- SELECT
168
- lower(hex(randomblob(16))),
169
- source_entity_id,
170
- target_entity_id,
171
- relation,
172
- merged_evidence,
173
- merged_first_seen_at,
174
- merged_last_seen_at
175
- FROM memory_entity_relation_merge
176
- `);
177
-
178
- raw.exec(/*sql*/ `DROP TABLE memory_entity_relation_merge`);
179
-
180
- raw.query(
181
- `INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
182
- ).run(checkpointKey, Date.now());
183
-
184
- raw.exec('COMMIT');
185
- } catch (e) {
186
- try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
187
- throw e;
188
- }
189
- }
190
-
191
- /**
192
- * Migrate from a column-level UNIQUE on fingerprint to a compound unique
193
- * index on (fingerprint, scope_id) so that the same item can exist in
194
- * different scopes independently.
195
- */
196
- export function migrateMemoryItemsFingerprintScopeUnique(database: Db): void {
197
- const raw = (database as unknown as { $client: Database }).$client;
198
- const checkpointKey = 'migration_memory_items_fingerprint_scope_unique_v1';
199
- const checkpoint = raw.query(
200
- `SELECT 1 FROM memory_checkpoints WHERE key = ?`,
201
- ).get(checkpointKey);
202
- if (checkpoint) return;
203
-
204
- // Check if the old column-level UNIQUE constraint still exists by inspecting
205
- // the CREATE TABLE DDL for the word UNIQUE (the PK also creates an autoindex,
206
- // so we cannot rely on sqlite_autoindex_* presence alone).
207
- const tableDdl = raw.query(
208
- `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'memory_items'`,
209
- ).get() as { sql: string } | null;
210
- if (!tableDdl || !tableDdl.sql.match(/fingerprint\s+TEXT\s+NOT\s+NULL\s+UNIQUE/i)) {
211
- // No column-level UNIQUE on fingerprint — either fresh DB or already migrated.
212
- raw.query(
213
- `INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
214
- ).run(checkpointKey, Date.now());
215
- return;
216
- }
217
-
218
- // Rebuild the table without the column-level UNIQUE constraint.
219
- raw.exec('PRAGMA foreign_keys = OFF');
220
- try {
221
- raw.exec('BEGIN');
222
-
223
- // Create new table without UNIQUE on fingerprint — all other columns
224
- // match the latest schema (including migration-added columns).
225
- raw.exec(/*sql*/ `
226
- CREATE TABLE memory_items_new (
227
- id TEXT PRIMARY KEY,
228
- kind TEXT NOT NULL,
229
- subject TEXT NOT NULL,
230
- statement TEXT NOT NULL,
231
- status TEXT NOT NULL,
232
- confidence REAL NOT NULL,
233
- fingerprint TEXT NOT NULL,
234
- first_seen_at INTEGER NOT NULL,
235
- last_seen_at INTEGER NOT NULL,
236
- last_used_at INTEGER,
237
- importance REAL,
238
- access_count INTEGER NOT NULL DEFAULT 0,
239
- valid_from INTEGER,
240
- invalid_at INTEGER,
241
- verification_state TEXT NOT NULL DEFAULT 'assistant_inferred',
242
- scope_id TEXT NOT NULL DEFAULT 'default'
243
- )
244
- `);
245
-
246
- raw.exec(/*sql*/ `
247
- INSERT INTO memory_items_new
248
- SELECT id, kind, subject, statement, status, confidence, fingerprint,
249
- first_seen_at, last_seen_at, last_used_at, importance, access_count,
250
- valid_from, invalid_at, verification_state, scope_id
251
- FROM memory_items
252
- `);
253
-
254
- raw.exec(/*sql*/ `DROP TABLE memory_items`);
255
- raw.exec(/*sql*/ `ALTER TABLE memory_items_new RENAME TO memory_items`);
256
-
257
- raw.query(
258
- `INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
259
- ).run(checkpointKey, Date.now());
260
-
261
- raw.exec('COMMIT');
262
- } catch (e) {
263
- try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
264
- throw e;
265
- } finally {
266
- raw.exec('PRAGMA foreign_keys = ON');
267
- }
268
- }
269
-
270
- /**
271
- * One-shot migration: recompute fingerprints for existing memory items to
272
- * include the scope_id prefix introduced in the scope-salted fingerprint PR.
273
- *
274
- * Old format: sha256(`${kind}|${subject.toLowerCase()}|${statement.toLowerCase()}`)
275
- * New format: sha256(`${scopeId}|${kind}|${subject.toLowerCase()}|${statement.toLowerCase()}`)
276
- *
277
- * Without this migration, pre-upgrade items would never match on re-extraction,
278
- * causing duplicates and broken deduplication.
279
- */
280
- export function migrateMemoryItemsScopeSaltedFingerprints(database: Db): void {
281
- const raw = (database as unknown as { $client: Database }).$client;
282
- const checkpointKey = 'migration_memory_items_scope_salted_fingerprints_v1';
283
- const checkpoint = raw.query(
284
- `SELECT 1 FROM memory_checkpoints WHERE key = ?`,
285
- ).get(checkpointKey);
286
- if (checkpoint) return;
287
-
288
- interface ItemRow {
289
- id: string;
290
- kind: string;
291
- subject: string;
292
- statement: string;
293
- scope_id: string;
294
- }
295
-
296
- const items = raw.query(
297
- `SELECT id, kind, subject, statement, scope_id FROM memory_items`,
298
- ).all() as ItemRow[];
299
-
300
- if (items.length === 0) {
301
- raw.query(
302
- `INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
303
- ).run(checkpointKey, Date.now());
304
- return;
305
- }
306
-
307
- try {
308
- raw.exec('BEGIN');
309
-
310
- const updateStmt = raw.prepare(
311
- `UPDATE memory_items SET fingerprint = ? WHERE id = ?`,
312
- );
313
-
314
- for (const item of items) {
315
- const fingerprint = computeMemoryFingerprint(item.scope_id, item.kind, item.subject, item.statement);
316
- updateStmt.run(fingerprint, item.id);
317
- }
318
-
319
- raw.query(
320
- `INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
321
- ).run(checkpointKey, Date.now());
322
-
323
- raw.exec('COMMIT');
324
- } catch (e) {
325
- try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
326
- throw e;
327
- }
328
- }
329
-
330
- /**
331
- * One-shot migration: normalize all assistant_id values in assistant-scoped tables
332
- * to "self" so they are visible after the daemon switched to the implicit single-tenant
333
- * identity.
334
- *
335
- * Before this change, rows were keyed by the real assistantId string passed via the
336
- * HTTP route. After the route change, all lookups use the constant "self". Without this
337
- * migration an upgraded daemon would see empty history / attachment lists for existing
338
- * data that was stored under the old assistantId.
339
- *
340
- * Affected tables:
341
- * - conversation_keys UNIQUE (assistant_id, conversation_key)
342
- * - attachments UNIQUE (assistant_id, content_hash) WHERE content_hash IS NOT NULL
343
- * - channel_inbound_events UNIQUE (assistant_id, source_channel, external_chat_id, external_message_id)
344
- * - message_runs no unique constraint on assistant_id
345
- *
346
- * Data-safety guarantees:
347
- * - conversation_keys: when a key exists under both 'self' and a real assistantId, the
348
- * 'self' row is updated to point to the real-assistantId conversation (which holds the
349
- * historical message thread). The 'self' conversation may be orphaned but is not deleted.
350
- * - attachments: message_attachments links are remapped to the surviving attachment before
351
- * any duplicate row is deleted, so no message loses its attachment metadata.
352
- * - channel_inbound_events: only delivery-tracking metadata, not user content; dedup
353
- * keeps one row per unique (channel, chat, message) tuple.
354
- * - All conversations and messages remain untouched — only assistant_id index columns
355
- * and key-lookup rows are modified.
356
- */
357
- export function migrateAssistantIdToSelf(database: Db): void {
358
- const raw = (database as unknown as { $client: Database }).$client;
359
- const checkpointKey = 'migration_normalize_assistant_id_to_self_v1';
360
- const checkpoint = raw.query(
361
- `SELECT 1 FROM memory_checkpoints WHERE key = ?`,
362
- ).get(checkpointKey);
363
- if (checkpoint) return;
364
-
365
- // On fresh installs the tables are created without assistant_id (PR 7+). Skip the
366
- // migration if NONE of the four affected tables have the column — pre-seed the
367
- // checkpoint so subsequent startups are also skipped. Checking all four (not just
368
- // conversation_keys) avoids a false negative on very old installs where
369
- // conversation_keys may not exist yet but other tables still carry assistant_id data.
370
- const affectedTables = ['conversation_keys', 'attachments', 'channel_inbound_events', 'message_runs'];
371
- const anyHasAssistantId = affectedTables.some((tbl) => {
372
- const ddl = raw.query(
373
- `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`,
374
- ).get(tbl) as { sql: string } | null;
375
- return ddl?.sql.includes('assistant_id') ?? false;
376
- });
377
- if (!anyHasAssistantId) {
378
- raw.query(
379
- `INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
380
- ).run(checkpointKey, Date.now());
381
- return;
382
- }
383
-
384
- // Helper: returns true if the given table's current DDL contains 'assistant_id'.
385
- const tableHasAssistantId = (tbl: string): boolean => {
386
- const ddl = raw.query(
387
- `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`,
388
- ).get(tbl) as { sql: string } | null;
389
- return ddl?.sql.includes('assistant_id') ?? false;
390
- };
391
-
392
- try {
393
- raw.exec('BEGIN');
394
-
395
- // Each section is guarded so that SQL referencing assistant_id is only executed
396
- // when the column still exists in that table. This handles mixed-schema states
397
- // (e.g., very old installs where some tables may already lack the column).
398
-
399
- // conversation_keys: UNIQUE (assistant_id, conversation_key)
400
- if (tableHasAssistantId('conversation_keys')) {
401
- // Step 1: Among non-self rows, keep only one per conversation_key so the
402
- // bulk UPDATE cannot hit a (non-self-A, key) + (non-self-B, key) collision.
403
- raw.exec(/*sql*/ `
404
- DELETE FROM conversation_keys
405
- WHERE assistant_id != 'self'
406
- AND rowid NOT IN (
407
- SELECT MIN(rowid) FROM conversation_keys
408
- WHERE assistant_id != 'self'
409
- GROUP BY conversation_key
410
- )
411
- `);
412
- // Step 2: For 'self' rows that have a non-self counterpart with the same
413
- // conversation_key, update the 'self' row to use the non-self row's
414
- // conversation_id. This preserves the historical conversation (which
415
- // has the message history from before the route change) rather than
416
- // discarding it in favour of a potentially-empty 'self' conversation.
417
- raw.exec(/*sql*/ `
418
- UPDATE conversation_keys
419
- SET conversation_id = (
420
- SELECT ck_ns.conversation_id
421
- FROM conversation_keys ck_ns
422
- WHERE ck_ns.assistant_id != 'self'
423
- AND ck_ns.conversation_key = conversation_keys.conversation_key
424
- ORDER BY ck_ns.rowid
425
- LIMIT 1
426
- )
427
- WHERE assistant_id = 'self'
428
- AND EXISTS (
429
- SELECT 1 FROM conversation_keys ck_ns
430
- WHERE ck_ns.assistant_id != 'self'
431
- AND ck_ns.conversation_key = conversation_keys.conversation_key
432
- )
433
- `);
434
- // Step 3: Delete the now-redundant non-self rows (their conversation_ids
435
- // have been preserved in the 'self' rows above).
436
- raw.exec(/*sql*/ `
437
- DELETE FROM conversation_keys
438
- WHERE assistant_id != 'self'
439
- AND EXISTS (
440
- SELECT 1 FROM conversation_keys ck2
441
- WHERE ck2.assistant_id = 'self'
442
- AND ck2.conversation_key = conversation_keys.conversation_key
443
- )
444
- `);
445
- // Step 4: Remaining non-self rows have no 'self' counterpart — safe to bulk-update.
446
- raw.exec(/*sql*/ `
447
- UPDATE conversation_keys SET assistant_id = 'self' WHERE assistant_id != 'self'
448
- `);
449
- }
450
-
451
- // attachments: UNIQUE (assistant_id, content_hash) WHERE content_hash IS NOT NULL
452
- //
453
- // message_attachments rows reference attachment IDs with ON DELETE CASCADE, so we
454
- // must remap links to the surviving row BEFORE deleting duplicates to avoid
455
- // silently dropping attachment metadata from messages.
456
- if (tableHasAssistantId('attachments')) {
457
- // Step 1: Remap message_attachments from non-self duplicates to their survivor
458
- // (MIN rowid per content_hash group), then delete the duplicates.
459
- raw.exec(/*sql*/ `
460
- UPDATE message_attachments
461
- SET attachment_id = (
462
- SELECT a_survivor.id
463
- FROM attachments a_survivor
464
- WHERE a_survivor.assistant_id != 'self'
465
- AND a_survivor.content_hash = (
466
- SELECT a_dup.content_hash FROM attachments a_dup
467
- WHERE a_dup.id = message_attachments.attachment_id
468
- )
469
- ORDER BY a_survivor.rowid
470
- LIMIT 1
471
- )
472
- WHERE attachment_id IN (
473
- SELECT id FROM attachments
474
- WHERE assistant_id != 'self'
475
- AND content_hash IS NOT NULL
476
- AND rowid NOT IN (
477
- SELECT MIN(rowid) FROM attachments
478
- WHERE assistant_id != 'self' AND content_hash IS NOT NULL
479
- GROUP BY content_hash
480
- )
481
- )
482
- `);
483
- raw.exec(/*sql*/ `
484
- DELETE FROM attachments
485
- WHERE assistant_id != 'self'
486
- AND content_hash IS NOT NULL
487
- AND rowid NOT IN (
488
- SELECT MIN(rowid) FROM attachments
489
- WHERE assistant_id != 'self'
490
- AND content_hash IS NOT NULL
491
- GROUP BY content_hash
492
- )
493
- `);
494
- // Step 2: Remap message_attachments from non-self rows conflicting with a 'self'
495
- // row to the 'self' row, then delete the now-unlinked non-self rows.
496
- raw.exec(/*sql*/ `
497
- UPDATE message_attachments
498
- SET attachment_id = (
499
- SELECT a_self.id
500
- FROM attachments a_self
501
- WHERE a_self.assistant_id = 'self'
502
- AND a_self.content_hash = (
503
- SELECT a_ns.content_hash FROM attachments a_ns
504
- WHERE a_ns.id = message_attachments.attachment_id
505
- )
506
- LIMIT 1
507
- )
508
- WHERE attachment_id IN (
509
- SELECT id FROM attachments
510
- WHERE assistant_id != 'self'
511
- AND content_hash IS NOT NULL
512
- AND EXISTS (
513
- SELECT 1 FROM attachments a2
514
- WHERE a2.assistant_id = 'self'
515
- AND a2.content_hash = attachments.content_hash
516
- )
517
- )
518
- `);
519
- raw.exec(/*sql*/ `
520
- DELETE FROM attachments
521
- WHERE assistant_id != 'self'
522
- AND content_hash IS NOT NULL
523
- AND EXISTS (
524
- SELECT 1 FROM attachments a2
525
- WHERE a2.assistant_id = 'self'
526
- AND a2.content_hash = attachments.content_hash
527
- )
528
- `);
529
- // Step 3: Bulk-update remaining non-self rows.
530
- raw.exec(/*sql*/ `
531
- UPDATE attachments SET assistant_id = 'self' WHERE assistant_id != 'self'
532
- `);
533
- }
534
-
535
- // channel_inbound_events: UNIQUE (assistant_id, source_channel, external_chat_id, external_message_id)
536
- if (tableHasAssistantId('channel_inbound_events')) {
537
- // Step 1: Dedup non-self rows sharing the same (source_channel, external_chat_id, external_message_id).
538
- raw.exec(/*sql*/ `
539
- DELETE FROM channel_inbound_events
540
- WHERE assistant_id != 'self'
541
- AND rowid NOT IN (
542
- SELECT MIN(rowid) FROM channel_inbound_events
543
- WHERE assistant_id != 'self'
544
- GROUP BY source_channel, external_chat_id, external_message_id
545
- )
546
- `);
547
- // Step 2: Delete non-self rows conflicting with existing 'self' rows.
548
- raw.exec(/*sql*/ `
549
- DELETE FROM channel_inbound_events
550
- WHERE assistant_id != 'self'
551
- AND EXISTS (
552
- SELECT 1 FROM channel_inbound_events e2
553
- WHERE e2.assistant_id = 'self'
554
- AND e2.source_channel = channel_inbound_events.source_channel
555
- AND e2.external_chat_id = channel_inbound_events.external_chat_id
556
- AND e2.external_message_id = channel_inbound_events.external_message_id
557
- )
558
- `);
559
- // Step 3: Bulk-update remaining non-self rows.
560
- raw.exec(/*sql*/ `
561
- UPDATE channel_inbound_events SET assistant_id = 'self' WHERE assistant_id != 'self'
562
- `);
563
- }
564
-
565
- // message_runs: no unique constraint on assistant_id — simple bulk update
566
- if (tableHasAssistantId('message_runs')) {
567
- raw.exec(/*sql*/ `
568
- UPDATE message_runs SET assistant_id = 'self' WHERE assistant_id != 'self'
569
- `);
570
- }
571
-
572
- raw.query(
573
- `INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
574
- ).run(checkpointKey, Date.now());
575
-
576
- raw.exec('COMMIT');
577
- } catch (e) {
578
- try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
579
- throw e;
580
- }
581
- }
582
-
583
- /**
584
- * One-shot migration: rebuild tables that previously stored assistant_id to remove
585
- * that column now that all rows are keyed to the implicit single-tenant identity ("self").
586
- *
587
- * Must run AFTER migrateAssistantIdToSelf (which normalises all values to "self")
588
- * so there are no constraint violations when recreating the tables without the
589
- * assistant_id dimension.
590
- *
591
- * Each table section is guarded by a DDL check so this is safe on fresh installs
592
- * where the column was never created in the first place.
593
- *
594
- * Tables rebuilt:
595
- * - conversation_keys UNIQUE (conversation_key)
596
- * - attachments no structural unique; content-dedup index updated
597
- * - channel_inbound_events UNIQUE (source_channel, external_chat_id, external_message_id)
598
- * - message_runs no unique constraint on assistant_id
599
- * - llm_usage_events nullable column with no constraint
600
- */
601
- export function migrateRemoveAssistantIdColumns(database: Db): void {
602
- const raw = (database as unknown as { $client: Database }).$client;
603
- const checkpointKey = 'migration_remove_assistant_id_columns_v1';
604
- const checkpoint = raw.query(
605
- `SELECT 1 FROM memory_checkpoints WHERE key = ?`,
606
- ).get(checkpointKey);
607
- if (checkpoint) return;
608
-
609
- raw.exec('PRAGMA foreign_keys = OFF');
610
- try {
611
- raw.exec('BEGIN');
612
-
613
- // --- conversation_keys ---
614
- const ckDdl = raw.query(
615
- `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'conversation_keys'`,
616
- ).get() as { sql: string } | null;
617
- if (ckDdl?.sql.includes('assistant_id')) {
618
- raw.exec(/*sql*/ `
619
- CREATE TABLE conversation_keys_new (
620
- id TEXT PRIMARY KEY,
621
- conversation_key TEXT NOT NULL UNIQUE,
622
- conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
623
- created_at INTEGER NOT NULL
624
- )
625
- `);
626
- raw.exec(/*sql*/ `
627
- INSERT INTO conversation_keys_new (id, conversation_key, conversation_id, created_at)
628
- SELECT id, conversation_key, conversation_id, created_at FROM conversation_keys
629
- `);
630
- raw.exec(/*sql*/ `DROP TABLE conversation_keys`);
631
- raw.exec(/*sql*/ `ALTER TABLE conversation_keys_new RENAME TO conversation_keys`);
632
- }
633
-
634
- // --- attachments ---
635
- const attDdl = raw.query(
636
- `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'attachments'`,
637
- ).get() as { sql: string } | null;
638
- if (attDdl?.sql.includes('assistant_id')) {
639
- raw.exec(/*sql*/ `
640
- CREATE TABLE attachments_new (
641
- id TEXT PRIMARY KEY,
642
- original_filename TEXT NOT NULL,
643
- mime_type TEXT NOT NULL,
644
- size_bytes INTEGER NOT NULL,
645
- kind TEXT NOT NULL,
646
- data_base64 TEXT NOT NULL,
647
- content_hash TEXT,
648
- thumbnail_base64 TEXT,
649
- created_at INTEGER NOT NULL
650
- )
651
- `);
652
- raw.exec(/*sql*/ `
653
- INSERT INTO attachments_new (id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at)
654
- SELECT id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at FROM attachments
655
- `);
656
- raw.exec(/*sql*/ `DROP TABLE attachments`);
657
- raw.exec(/*sql*/ `ALTER TABLE attachments_new RENAME TO attachments`);
658
- }
659
-
660
- // --- channel_inbound_events ---
661
- const cieDdl = raw.query(
662
- `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'channel_inbound_events'`,
663
- ).get() as { sql: string } | null;
664
- if (cieDdl?.sql.includes('assistant_id')) {
665
- raw.exec(/*sql*/ `
666
- CREATE TABLE channel_inbound_events_new (
667
- id TEXT PRIMARY KEY,
668
- source_channel TEXT NOT NULL,
669
- external_chat_id TEXT NOT NULL,
670
- external_message_id TEXT NOT NULL,
671
- source_message_id TEXT,
672
- conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
673
- message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,
674
- delivery_status TEXT NOT NULL DEFAULT 'pending',
675
- processing_status TEXT NOT NULL DEFAULT 'pending',
676
- processing_attempts INTEGER NOT NULL DEFAULT 0,
677
- last_processing_error TEXT,
678
- retry_after INTEGER,
679
- raw_payload TEXT,
680
- created_at INTEGER NOT NULL,
681
- updated_at INTEGER NOT NULL,
682
- UNIQUE (source_channel, external_chat_id, external_message_id)
683
- )
684
- `);
685
- raw.exec(/*sql*/ `
686
- INSERT INTO channel_inbound_events_new (
687
- id, source_channel, external_chat_id, external_message_id, source_message_id,
688
- conversation_id, message_id, delivery_status, processing_status,
689
- processing_attempts, last_processing_error, retry_after, raw_payload,
690
- created_at, updated_at
691
- )
692
- SELECT
693
- id, source_channel, external_chat_id, external_message_id, source_message_id,
694
- conversation_id, message_id, delivery_status, processing_status,
695
- processing_attempts, last_processing_error, retry_after, raw_payload,
696
- created_at, updated_at
697
- FROM channel_inbound_events
698
- `);
699
- raw.exec(/*sql*/ `DROP TABLE channel_inbound_events`);
700
- raw.exec(/*sql*/ `ALTER TABLE channel_inbound_events_new RENAME TO channel_inbound_events`);
701
- }
702
-
703
- // --- message_runs ---
704
- const mrDdl = raw.query(
705
- `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'message_runs'`,
706
- ).get() as { sql: string } | null;
707
- if (mrDdl?.sql.includes('assistant_id')) {
708
- raw.exec(/*sql*/ `
709
- CREATE TABLE message_runs_new (
710
- id TEXT PRIMARY KEY,
711
- conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
712
- message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,
713
- status TEXT NOT NULL DEFAULT 'running',
714
- pending_confirmation TEXT,
715
- input_tokens INTEGER NOT NULL DEFAULT 0,
716
- output_tokens INTEGER NOT NULL DEFAULT 0,
717
- estimated_cost REAL NOT NULL DEFAULT 0,
718
- error TEXT,
719
- created_at INTEGER NOT NULL,
720
- updated_at INTEGER NOT NULL
721
- )
722
- `);
723
- raw.exec(/*sql*/ `
724
- INSERT INTO message_runs_new (
725
- id, conversation_id, message_id, status, pending_confirmation,
726
- input_tokens, output_tokens, estimated_cost, error, created_at, updated_at
727
- )
728
- SELECT
729
- id, conversation_id, message_id, status, pending_confirmation,
730
- input_tokens, output_tokens, estimated_cost, error, created_at, updated_at
731
- FROM message_runs
732
- `);
733
- raw.exec(/*sql*/ `DROP TABLE message_runs`);
734
- raw.exec(/*sql*/ `ALTER TABLE message_runs_new RENAME TO message_runs`);
735
- }
736
-
737
- // --- llm_usage_events ---
738
- const lueDdl = raw.query(
739
- `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'llm_usage_events'`,
740
- ).get() as { sql: string } | null;
741
- if (lueDdl?.sql.includes('assistant_id')) {
742
- raw.exec(/*sql*/ `
743
- CREATE TABLE llm_usage_events_new (
744
- id TEXT PRIMARY KEY,
745
- created_at INTEGER NOT NULL,
746
- conversation_id TEXT,
747
- run_id TEXT,
748
- request_id TEXT,
749
- actor TEXT NOT NULL,
750
- provider TEXT NOT NULL,
751
- model TEXT NOT NULL,
752
- input_tokens INTEGER NOT NULL,
753
- output_tokens INTEGER NOT NULL,
754
- cache_creation_input_tokens INTEGER,
755
- cache_read_input_tokens INTEGER,
756
- estimated_cost_usd REAL,
757
- pricing_status TEXT NOT NULL,
758
- metadata_json TEXT
759
- )
760
- `);
761
- raw.exec(/*sql*/ `
762
- INSERT INTO llm_usage_events_new (
763
- id, created_at, conversation_id, run_id, request_id, actor, provider, model,
764
- input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens,
765
- estimated_cost_usd, pricing_status, metadata_json
766
- )
767
- SELECT
768
- id, created_at, conversation_id, run_id, request_id, actor, provider, model,
769
- input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens,
770
- estimated_cost_usd, pricing_status, metadata_json
771
- FROM llm_usage_events
772
- `);
773
- raw.exec(/*sql*/ `DROP TABLE llm_usage_events`);
774
- raw.exec(/*sql*/ `ALTER TABLE llm_usage_events_new RENAME TO llm_usage_events`);
775
- }
776
-
777
- raw.query(
778
- `INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
779
- ).run(checkpointKey, Date.now());
780
-
781
- raw.exec('COMMIT');
782
- } catch (e) {
783
- try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
784
- throw e;
785
- } finally {
786
- raw.exec('PRAGMA foreign_keys = ON');
787
- }
788
- }
789
-
790
- /**
791
- * One-shot migration: rebuild llm_usage_events to drop the assistant_id column.
792
- *
793
- * This is a SEPARATE migration from migrateRemoveAssistantIdColumns so that installs
794
- * where the 4-table version of that migration already ran (checkpoint already set)
795
- * still get the llm_usage_events column removed. Without a separate checkpoint key,
796
- * those installs would skip the llm_usage_events rebuild entirely.
797
- *
798
- * Safe on fresh installs (DDL guard exits early) and idempotent via checkpoint.
799
- */
800
- export function migrateLlmUsageEventsDropAssistantId(database: Db): void {
801
- const raw = (database as unknown as { $client: Database }).$client;
802
- const checkpointKey = 'migration_remove_assistant_id_lue_v1';
803
- const checkpoint = raw.query(
804
- `SELECT 1 FROM memory_checkpoints WHERE key = ?`,
805
- ).get(checkpointKey);
806
- if (checkpoint) return;
807
-
808
- // DDL guard: if the column was already removed (fresh install or migrateRemoveAssistantIdColumns
809
- // ran with the llm_usage_events block), just record the checkpoint and exit.
810
- const lueDdl = raw.query(
811
- `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'llm_usage_events'`,
812
- ).get() as { sql: string } | null;
813
-
814
- if (!lueDdl?.sql.includes('assistant_id')) {
815
- raw.query(
816
- `INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
817
- ).run(checkpointKey, Date.now());
818
- return;
819
- }
820
-
821
- raw.exec('PRAGMA foreign_keys = OFF');
822
- try {
823
- raw.exec('BEGIN');
824
-
825
- raw.exec(/*sql*/ `
826
- CREATE TABLE llm_usage_events_new (
827
- id TEXT PRIMARY KEY,
828
- created_at INTEGER NOT NULL,
829
- conversation_id TEXT,
830
- run_id TEXT,
831
- request_id TEXT,
832
- actor TEXT NOT NULL,
833
- provider TEXT NOT NULL,
834
- model TEXT NOT NULL,
835
- input_tokens INTEGER NOT NULL,
836
- output_tokens INTEGER NOT NULL,
837
- cache_creation_input_tokens INTEGER,
838
- cache_read_input_tokens INTEGER,
839
- estimated_cost_usd REAL,
840
- pricing_status TEXT NOT NULL,
841
- metadata_json TEXT
842
- )
843
- `);
844
- raw.exec(/*sql*/ `
845
- INSERT INTO llm_usage_events_new (
846
- id, created_at, conversation_id, run_id, request_id, actor, provider, model,
847
- input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens,
848
- estimated_cost_usd, pricing_status, metadata_json
849
- )
850
- SELECT
851
- id, created_at, conversation_id, run_id, request_id, actor, provider, model,
852
- input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens,
853
- estimated_cost_usd, pricing_status, metadata_json
854
- FROM llm_usage_events
855
- `);
856
- raw.exec(/*sql*/ `DROP TABLE llm_usage_events`);
857
- raw.exec(/*sql*/ `ALTER TABLE llm_usage_events_new RENAME TO llm_usage_events`);
858
-
859
- raw.query(
860
- `INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
861
- ).run(checkpointKey, Date.now());
862
-
863
- raw.exec('COMMIT');
864
- } catch (e) {
865
- try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
866
- throw e;
867
- } finally {
868
- raw.exec('PRAGMA foreign_keys = ON');
869
- }
870
- }
871
-
872
- /**
873
- * One-shot migration: deduplicate external_conversation_bindings rows that
874
- * share the same (source_channel, external_chat_id), then create a unique
875
- * index to enforce the invariant at DB level.
876
- *
877
- * For each duplicate group, the binding with the newest updatedAt (then
878
- * createdAt) is kept; older duplicates are deleted.
879
- */
880
- export function migrateExtConvBindingsChannelChatUnique(database: Db): void {
881
- const raw = (database as unknown as { $client: Database }).$client;
882
-
883
- // If the unique index already exists, nothing to do.
884
- const idxExists = raw.query(
885
- `SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = 'idx_ext_conv_bindings_channel_chat_unique'`,
886
- ).get();
887
- if (idxExists) return;
888
-
889
- // Check if the table exists (first boot edge case).
890
- const tableExists = raw.query(
891
- `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'external_conversation_bindings'`,
892
- ).get();
893
- if (!tableExists) return;
894
-
895
- // Remove duplicates: keep the row with the newest updatedAt, then createdAt.
896
- // Since conversation_id is the PK (rowid alias), we use it for ordering ties.
897
- try {
898
- raw.exec('BEGIN');
899
-
900
- raw.exec(/*sql*/ `
901
- DELETE FROM external_conversation_bindings
902
- WHERE rowid NOT IN (
903
- SELECT rowid FROM (
904
- SELECT rowid,
905
- ROW_NUMBER() OVER (
906
- PARTITION BY source_channel, external_chat_id
907
- ORDER BY updated_at DESC, created_at DESC, rowid DESC
908
- ) AS rn
909
- FROM external_conversation_bindings
910
- )
911
- WHERE rn = 1
912
- )
913
- `);
914
-
915
- raw.exec(/*sql*/ `
916
- CREATE UNIQUE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel_chat_unique
917
- ON external_conversation_bindings(source_channel, external_chat_id)
918
- `);
919
-
920
- raw.exec('COMMIT');
921
- } catch (e) {
922
- try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
923
- throw e;
924
- }
925
- }
926
-
927
- /**
928
- * One-shot migration: remove duplicate (provider, provider_call_sid) rows from
929
- * call_sessions so that the unique index can be created safely on upgraded databases
930
- * that pre-date the constraint.
931
- *
932
- * For each set of duplicates, the most recently updated row is kept.
933
- */
934
- export function migrateCallSessionsProviderSidDedup(database: Db): void {
935
- const raw = (database as unknown as { $client: Database }).$client;
936
-
937
- // Quick check: if the unique index already exists, no dedup is needed.
938
- const idxExists = raw.query(
939
- `SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = 'idx_call_sessions_provider_sid_unique'`,
940
- ).get();
941
- if (idxExists) return;
942
-
943
- // Check if the table even exists yet (first boot).
944
- const tableExists = raw.query(
945
- `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'call_sessions'`,
946
- ).get();
947
- if (!tableExists) return;
948
-
949
- // Count duplicates before doing any work.
950
- const dupCount = raw.query(/*sql*/ `
951
- SELECT COUNT(*) AS c FROM (
952
- SELECT provider, provider_call_sid
953
- FROM call_sessions
954
- WHERE provider_call_sid IS NOT NULL
955
- GROUP BY provider, provider_call_sid
956
- HAVING COUNT(*) > 1
957
- )
958
- `).get() as { c: number } | null;
959
-
960
- if (!dupCount || dupCount.c === 0) return;
961
-
962
- log.warn({ duplicateGroups: dupCount.c }, 'Deduplicating call_sessions with duplicate provider_call_sid before creating unique index');
963
-
964
- try {
965
- raw.exec('BEGIN');
966
-
967
- // Keep the most recently updated row per (provider, provider_call_sid);
968
- // delete the rest.
969
- raw.exec(/*sql*/ `
970
- DELETE FROM call_sessions
971
- WHERE provider_call_sid IS NOT NULL
972
- AND rowid NOT IN (
973
- SELECT MAX(rowid) FROM call_sessions
974
- WHERE provider_call_sid IS NOT NULL
975
- GROUP BY provider, provider_call_sid
976
- )
977
- `);
978
-
979
- raw.exec('COMMIT');
980
- } catch (e) {
981
- try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
982
- throw e;
983
- }
984
- }
1
+ // Re-export all migrations from individual files for backward compatibility.
2
+ export {
3
+ type MigrationRegistryEntry,
4
+ MIGRATION_REGISTRY,
5
+ type MigrationValidationResult,
6
+ validateMigrationState,
7
+ migrateJobDeferrals,
8
+ migrateToolInvocationsFk,
9
+ migrateMemoryFtsBackfill,
10
+ migrateMemoryEntityRelationDedup,
11
+ migrateMemoryItemsFingerprintScopeUnique,
12
+ migrateMemoryItemsScopeSaltedFingerprints,
13
+ migrateAssistantIdToSelf,
14
+ migrateRemoveAssistantIdColumns,
15
+ migrateLlmUsageEventsDropAssistantId,
16
+ migrateExtConvBindingsChannelChatUnique,
17
+ migrateCallSessionsProviderSidDedup,
18
+ migrateCallSessionsAddInitiatedFrom,
19
+ migrateGuardianActionTables,
20
+ migrateBackfillInboxThreadStateFromBindings,
21
+ migrateDropActiveSearchIndex,
22
+ migrateMemorySegmentsIndexes,
23
+ migrateMemoryItemsIndexes,
24
+ migrateRemainingTableIndexes,
25
+ } from './migrations/index.js';