@vellumai/assistant 0.5.5 → 0.5.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 (382) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +4 -5
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docs/architecture/keychain-broker.md +45 -240
  7. package/docs/architecture/security.md +0 -17
  8. package/docs/credential-execution-service.md +2 -2
  9. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
  11. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  12. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  13. package/package.json +2 -3
  14. package/src/__tests__/actor-token-service.test.ts +1 -2
  15. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  16. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  17. package/src/__tests__/btw-routes.test.ts +0 -39
  18. package/src/__tests__/call-domain.test.ts +0 -128
  19. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  21. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  22. package/src/__tests__/checker.test.ts +4 -2
  23. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  24. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  25. package/src/__tests__/config-schema.test.ts +3 -3
  26. package/src/__tests__/context-window-manager.test.ts +78 -0
  27. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  28. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  29. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  30. package/src/__tests__/conversation-title-service.test.ts +117 -1
  31. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  32. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  33. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  34. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  35. package/src/__tests__/credentials-cli.test.ts +78 -0
  36. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  37. package/src/__tests__/docker-signing-key-bootstrap.test.ts +98 -0
  38. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  39. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  40. package/src/__tests__/host-shell-tool.test.ts +6 -7
  41. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  42. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  43. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  44. package/src/__tests__/intent-routing.test.ts +0 -13
  45. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  46. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  47. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  48. package/src/__tests__/memory-regressions.test.ts +8 -30
  49. package/src/__tests__/migration-export-http.test.ts +2 -2
  50. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  51. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  52. package/src/__tests__/migration-validate-http.test.ts +2 -2
  53. package/src/__tests__/non-member-access-request.test.ts +0 -5
  54. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  55. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  56. package/src/__tests__/permission-types.test.ts +1 -0
  57. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  58. package/src/__tests__/qdrant-manager.test.ts +28 -2
  59. package/src/__tests__/registry.test.ts +0 -6
  60. package/src/__tests__/require-fresh-approval.test.ts +4 -0
  61. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  62. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  63. package/src/__tests__/secure-keys.test.ts +83 -263
  64. package/src/__tests__/shell-identity.test.ts +96 -6
  65. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  66. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  67. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  68. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  69. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  70. package/src/__tests__/skill-load-tool.test.ts +0 -2
  71. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  72. package/src/__tests__/skills.test.ts +0 -2
  73. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  74. package/src/__tests__/suggestion-routes.test.ts +1 -32
  75. package/src/__tests__/system-prompt.test.ts +0 -1
  76. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
  77. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  78. package/src/__tests__/tool-executor.test.ts +4 -0
  79. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  80. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  81. package/src/__tests__/update-bulletin.test.ts +0 -2
  82. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  83. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  84. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  85. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  86. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  87. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  88. package/src/calls/audio-store.test.ts +97 -0
  89. package/src/calls/audio-store.ts +205 -0
  90. package/src/calls/call-controller.ts +85 -7
  91. package/src/calls/call-domain.ts +3 -0
  92. package/src/calls/call-store.ts +10 -3
  93. package/src/calls/fish-audio-client.ts +117 -0
  94. package/src/calls/relay-server.ts +27 -0
  95. package/src/calls/twilio-routes.ts +2 -1
  96. package/src/calls/types.ts +1 -0
  97. package/src/calls/voice-ingress-preflight.ts +0 -42
  98. package/src/calls/voice-quality.ts +26 -5
  99. package/src/calls/voice-session-bridge.ts +6 -12
  100. package/src/cli/commands/config.ts +1 -4
  101. package/src/cli/commands/conversations.ts +0 -18
  102. package/src/cli/commands/credentials.ts +34 -4
  103. package/src/cli/commands/oauth/index.ts +7 -0
  104. package/src/cli/commands/oauth/platform.ts +179 -0
  105. package/src/cli/commands/platform.ts +3 -3
  106. package/src/config/assistant-feature-flags.ts +186 -5
  107. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  108. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  109. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  110. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  111. package/src/config/bundled-tool-registry.ts +1 -11
  112. package/src/config/env-registry.ts +1 -1
  113. package/src/config/env.ts +16 -16
  114. package/src/config/feature-flag-registry.json +48 -16
  115. package/src/config/loader.ts +98 -31
  116. package/src/config/schema.ts +4 -25
  117. package/src/config/schemas/calls.ts +13 -0
  118. package/src/config/schemas/fish-audio.ts +39 -0
  119. package/src/config/schemas/memory.ts +0 -4
  120. package/src/config/schemas/platform.ts +1 -1
  121. package/src/config/schemas/security.ts +4 -4
  122. package/src/config/types.ts +0 -1
  123. package/src/contacts/contact-store.ts +39 -0
  124. package/src/contacts/types.ts +2 -0
  125. package/src/context/window-manager.ts +53 -2
  126. package/src/credential-execution/approval-bridge.ts +1 -0
  127. package/src/credential-execution/executable-discovery.ts +28 -4
  128. package/src/credential-execution/feature-gates.ts +16 -0
  129. package/src/credential-execution/process-manager.ts +38 -0
  130. package/src/daemon/assistant-attachments.ts +9 -0
  131. package/src/daemon/config-watcher.ts +6 -4
  132. package/src/daemon/conversation-agent-loop.ts +0 -60
  133. package/src/daemon/conversation-memory.ts +0 -117
  134. package/src/daemon/conversation-runtime-assembly.ts +0 -2
  135. package/src/daemon/conversation-tool-setup.ts +0 -105
  136. package/src/daemon/conversation.ts +10 -1
  137. package/src/daemon/handlers/config-vercel.ts +92 -0
  138. package/src/daemon/handlers/conversations.ts +0 -11
  139. package/src/daemon/handlers/skills.ts +2 -15
  140. package/src/daemon/install-symlink.ts +195 -0
  141. package/src/daemon/lifecycle.ts +229 -96
  142. package/src/daemon/message-types/conversations.ts +3 -4
  143. package/src/daemon/message-types/diagnostics.ts +3 -22
  144. package/src/daemon/message-types/messages.ts +0 -2
  145. package/src/daemon/message-types/upgrades.ts +8 -0
  146. package/src/daemon/server.ts +30 -92
  147. package/src/events/domain-events.ts +2 -1
  148. package/src/followups/followup-store.ts +5 -2
  149. package/src/inbound/platform-callback-registration.ts +3 -3
  150. package/src/instrument.ts +8 -5
  151. package/src/memory/conversation-crud.ts +0 -236
  152. package/src/memory/conversation-title-service.ts +76 -11
  153. package/src/memory/db-init.ts +15 -11
  154. package/src/memory/indexer.ts +15 -106
  155. package/src/memory/items-extractor.ts +15 -1
  156. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  157. package/src/memory/job-handlers/embedding.ts +0 -79
  158. package/src/memory/job-utils.ts +1 -1
  159. package/src/memory/jobs-store.ts +30 -13
  160. package/src/memory/jobs-worker.ts +31 -27
  161. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  162. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  163. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  164. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  165. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  166. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  167. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  168. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  169. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  170. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  171. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  172. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  173. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  174. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  175. package/src/memory/migrations/116-messages-fts.ts +106 -1
  176. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  177. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  178. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  179. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  180. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  181. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  182. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  183. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  184. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  185. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  186. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  187. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  188. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  189. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  190. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  191. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  192. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  193. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  194. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  195. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  196. package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
  197. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  198. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  199. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  200. package/src/memory/migrations/index.ts +5 -3
  201. package/src/memory/migrations/registry.ts +90 -0
  202. package/src/memory/migrations/validate-migration-state.ts +137 -11
  203. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  204. package/src/memory/qdrant-client.ts +4 -6
  205. package/src/memory/qdrant-manager.ts +64 -7
  206. package/src/memory/schema/calls.ts +1 -0
  207. package/src/memory/schema/contacts.ts +1 -0
  208. package/src/memory/schema/conversations.ts +0 -3
  209. package/src/memory/schema/index.ts +0 -2
  210. package/src/messaging/draft-store.ts +2 -2
  211. package/src/notifications/decision-engine.ts +4 -1
  212. package/src/oauth/connection-resolver.ts +6 -4
  213. package/src/permissions/checker.ts +0 -38
  214. package/src/permissions/defaults.ts +3 -3
  215. package/src/permissions/shell-identity.ts +76 -22
  216. package/src/permissions/trust-client.ts +2 -13
  217. package/src/permissions/trust-store.ts +8 -3
  218. package/src/permissions/types.ts +4 -2
  219. package/src/platform/client.ts +35 -7
  220. package/src/prompts/persona-resolver.ts +138 -0
  221. package/src/prompts/system-prompt.ts +36 -4
  222. package/src/prompts/templates/users/default.md +1 -0
  223. package/src/providers/registry.ts +27 -40
  224. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  225. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  226. package/src/runtime/auth/external-assistant-id.ts +13 -59
  227. package/src/runtime/auth/route-policy.ts +29 -1
  228. package/src/runtime/auth/token-service.ts +53 -15
  229. package/src/runtime/channel-readiness-service.ts +1 -16
  230. package/src/runtime/http-server.ts +29 -2
  231. package/src/runtime/middleware/error-handler.ts +1 -9
  232. package/src/runtime/routes/audio-routes.ts +40 -0
  233. package/src/runtime/routes/btw-routes.ts +0 -17
  234. package/src/runtime/routes/conversation-management-routes.ts +0 -36
  235. package/src/runtime/routes/conversation-query-routes.ts +106 -2
  236. package/src/runtime/routes/conversation-routes.ts +4 -43
  237. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  238. package/src/runtime/routes/identity-routes.ts +18 -29
  239. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  240. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  241. package/src/runtime/routes/integrations/vercel.ts +89 -0
  242. package/src/runtime/routes/log-export-routes.ts +5 -0
  243. package/src/runtime/routes/memory-item-routes.test.ts +221 -3
  244. package/src/runtime/routes/memory-item-routes.ts +144 -4
  245. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  246. package/src/runtime/routes/migration-routes.ts +17 -1
  247. package/src/runtime/routes/notification-routes.ts +58 -0
  248. package/src/runtime/routes/schedule-routes.ts +65 -0
  249. package/src/runtime/routes/settings-routes.ts +41 -1
  250. package/src/runtime/routes/tts-routes.ts +86 -0
  251. package/src/runtime/routes/upgrade-broadcast-routes.ts +175 -0
  252. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  253. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  254. package/src/runtime/routes/workspace-routes.ts +1 -1
  255. package/src/runtime/routes/workspace-utils.ts +86 -2
  256. package/src/schedule/schedule-store.ts +0 -21
  257. package/src/security/ces-credential-client.ts +59 -22
  258. package/src/security/ces-rpc-credential-backend.ts +85 -0
  259. package/src/security/credential-backend.ts +12 -88
  260. package/src/security/keychain-broker-client.ts +10 -2
  261. package/src/security/secure-keys.ts +94 -113
  262. package/src/skills/catalog-install.ts +13 -7
  263. package/src/skills/inline-command-render.ts +5 -1
  264. package/src/skills/inline-command-runner.ts +30 -2
  265. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  266. package/src/tools/calls/call-start.ts +1 -0
  267. package/src/tools/executor.ts +0 -4
  268. package/src/tools/memory/handlers.ts +1 -129
  269. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  270. package/src/tools/network/web-fetch.ts +3 -1
  271. package/src/tools/permission-checker.ts +18 -0
  272. package/src/tools/skills/execute.ts +1 -1
  273. package/src/tools/skills/load.ts +9 -2
  274. package/src/tools/types.ts +0 -8
  275. package/src/util/errors.ts +0 -12
  276. package/src/util/platform.ts +8 -55
  277. package/src/util/xml.ts +8 -0
  278. package/src/workspace/git-service.ts +5 -2
  279. package/src/workspace/heartbeat-service.ts +5 -24
  280. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  281. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  282. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  283. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  284. package/src/workspace/migrations/006-services-config.ts +49 -0
  285. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  286. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  287. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  288. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  289. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  290. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  291. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  292. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  293. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  294. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  295. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  296. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  297. package/src/workspace/migrations/registry.ts +8 -0
  298. package/src/workspace/migrations/runner.ts +106 -2
  299. package/src/workspace/migrations/types.ts +4 -0
  300. package/src/__tests__/archive-recall.test.ts +0 -560
  301. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  302. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  303. package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
  304. package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
  305. package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
  306. package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
  307. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
  308. package/src/__tests__/diagnostics-export.test.ts +0 -288
  309. package/src/__tests__/local-gateway-health.test.ts +0 -209
  310. package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
  311. package/src/__tests__/memory-brief-time.test.ts +0 -285
  312. package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
  313. package/src/__tests__/memory-chunk-archive.test.ts +0 -400
  314. package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
  315. package/src/__tests__/memory-episode-archive.test.ts +0 -370
  316. package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
  317. package/src/__tests__/memory-observation-archive.test.ts +0 -375
  318. package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
  319. package/src/__tests__/memory-reducer-job.test.ts +0 -538
  320. package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
  321. package/src/__tests__/memory-reducer-store.test.ts +0 -728
  322. package/src/__tests__/memory-reducer-types.test.ts +0 -707
  323. package/src/__tests__/memory-reducer.test.ts +0 -704
  324. package/src/__tests__/memory-simplified-config.test.ts +0 -281
  325. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  326. package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
  327. package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
  328. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  329. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  330. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  331. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  332. package/src/__tests__/swarm-recursion.test.ts +0 -197
  333. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  334. package/src/__tests__/swarm-tool.test.ts +0 -185
  335. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  336. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  337. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  338. package/src/commands/cc-command-registry.ts +0 -248
  339. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  340. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  341. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  342. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  343. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  344. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  345. package/src/config/schemas/memory-simplified.ts +0 -101
  346. package/src/config/schemas/swarm.ts +0 -82
  347. package/src/logfire.ts +0 -135
  348. package/src/memory/archive-recall.ts +0 -516
  349. package/src/memory/archive-store.ts +0 -400
  350. package/src/memory/brief-formatting.ts +0 -33
  351. package/src/memory/brief-open-loops.ts +0 -266
  352. package/src/memory/brief-time.ts +0 -162
  353. package/src/memory/brief.ts +0 -75
  354. package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
  355. package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
  356. package/src/memory/migrations/185-memory-brief-state.ts +0 -52
  357. package/src/memory/migrations/186-memory-archive.ts +0 -109
  358. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
  359. package/src/memory/reducer-scheduler.ts +0 -242
  360. package/src/memory/reducer-store.ts +0 -271
  361. package/src/memory/reducer-types.ts +0 -106
  362. package/src/memory/reducer.ts +0 -467
  363. package/src/memory/schema/memory-archive.ts +0 -121
  364. package/src/memory/schema/memory-brief.ts +0 -55
  365. package/src/runtime/local-gateway-health.ts +0 -275
  366. package/src/security/secret-ingress.ts +0 -68
  367. package/src/swarm/backend-claude-code.ts +0 -225
  368. package/src/swarm/checkpoint.ts +0 -137
  369. package/src/swarm/graph-utils.ts +0 -53
  370. package/src/swarm/index.ts +0 -55
  371. package/src/swarm/limits.ts +0 -66
  372. package/src/swarm/orchestrator.ts +0 -424
  373. package/src/swarm/plan-validator.ts +0 -117
  374. package/src/swarm/router-planner.ts +0 -162
  375. package/src/swarm/router-prompts.ts +0 -39
  376. package/src/swarm/synthesizer.ts +0 -81
  377. package/src/swarm/types.ts +0 -72
  378. package/src/swarm/worker-backend.ts +0 -131
  379. package/src/swarm/worker-prompts.ts +0 -80
  380. package/src/swarm/worker-runner.ts +0 -170
  381. package/src/tools/claude-code/claude-code.ts +0 -610
  382. package/src/tools/swarm/delegate.ts +0 -205
@@ -4,6 +4,10 @@ import { v4 as uuid } from "uuid";
4
4
  import { getLogger } from "../util/logger.js";
5
5
  import { truncate } from "../util/truncate.js";
6
6
  import { getDb, rawAll, rawChanges } from "./db.js";
7
+ import {
8
+ isQdrantBreakerOpen,
9
+ shouldAllowQdrantProbe,
10
+ } from "./qdrant-circuit-breaker.js";
7
11
  import { memoryJobs } from "./schema.js";
8
12
 
9
13
  const log = getLogger("memory-jobs-store");
@@ -12,9 +16,6 @@ export type MemoryJobType =
12
16
  | "embed_segment"
13
17
  | "embed_item"
14
18
  | "embed_summary"
15
- | "embed_chunk"
16
- | "embed_episode"
17
- | "embed_observation"
18
19
  | "extract_items"
19
20
  | "extract_entities"
20
21
  | "cleanup_stale_superseded_items"
@@ -30,8 +31,6 @@ export type MemoryJobType =
30
31
  | "embed_media"
31
32
  | "embed_attachment"
32
33
  | "generate_conversation_starters"
33
- | "reduce_conversation_memory"
34
- | "backfill_simplified_memory"
35
34
  | "generate_capability_cards" // legacy compat — silently dropped by worker (capability cards removed)
36
35
  | "generate_thread_starters"; // legacy compat — silently dropped by worker (renamed to generate_conversation_starters)
37
36
 
@@ -39,9 +38,6 @@ const EMBED_JOB_TYPES: MemoryJobType[] = [
39
38
  "embed_segment",
40
39
  "embed_item",
41
40
  "embed_summary",
42
- "embed_chunk",
43
- "embed_episode",
44
- "embed_observation",
45
41
  "embed_media",
46
42
  "embed_attachment",
47
43
  ];
@@ -209,14 +205,33 @@ export function claimMemoryJobs(limit: number): MemoryJob[] {
209
205
  .all();
210
206
 
211
207
  const remainingSlots = limit - nonEmbedCandidates.length;
208
+
209
+ // When the Qdrant circuit breaker is open, skip embed jobs entirely —
210
+ // they would just be claimed → fail → deferred, wasting CPU cycles.
211
+ // Exception: if the cooldown has elapsed (breaker ready for half-open probe),
212
+ // allow exactly 1 embed job through so the breaker can self-heal.
213
+ const breakerOpen = isQdrantBreakerOpen();
214
+ const probeAllowed = breakerOpen && shouldAllowQdrantProbe();
215
+ const skipEmbedJobs = breakerOpen && !probeAllowed;
216
+ const embedLimit = probeAllowed ? 1 : remainingSlots;
217
+
218
+ if (skipEmbedJobs && remainingSlots > 0) {
219
+ log.debug("Skipping embed job claims — Qdrant circuit breaker is open");
220
+ }
221
+ if (probeAllowed && remainingSlots > 0) {
222
+ log.debug(
223
+ "Allowing 1 embed probe job — Qdrant circuit breaker cooldown elapsed",
224
+ );
225
+ }
226
+
212
227
  const embedCandidates =
213
- remainingSlots > 0
228
+ remainingSlots > 0 && !skipEmbedJobs
214
229
  ? db
215
230
  .select()
216
231
  .from(memoryJobs)
217
232
  .where(and(pendingFilter, inArray(memoryJobs.type, EMBED_JOB_TYPES)))
218
233
  .orderBy(asc(memoryJobs.runAfter), asc(memoryJobs.createdAt))
219
- .limit(remainingSlots)
234
+ .limit(embedLimit)
220
235
  .all()
221
236
  : [];
222
237
 
@@ -251,8 +266,8 @@ export function completeMemoryJob(id: string): void {
251
266
 
252
267
  /** Max times a job can be deferred before it is marked as failed. */
253
268
  const MAX_DEFERRALS = 50;
254
- /** Warn when deferrals reach 80% of the limit. */
255
- const DEFER_WARNING_THRESHOLD = Math.floor(MAX_DEFERRALS * 0.8);
269
+ /** Log warnings at these milestone counts to avoid flooding logs. */
270
+ const DEFERRAL_WARN_MILESTONES = [40, 45];
256
271
  /** Base delay in ms for deferred jobs (grows with exponential backoff). */
257
272
  const DEFER_BASE_DELAY_MS = 30_000;
258
273
  /** Maximum delay cap for deferred jobs (5 minutes). */
@@ -294,7 +309,9 @@ export function deferMemoryJob(id: string): "deferred" | "failed" {
294
309
  return "failed";
295
310
  }
296
311
 
297
- if (deferrals >= DEFER_WARNING_THRESHOLD) {
312
+ // Log at milestones only (40, 45) to avoid flooding logs.
313
+ // At 50, the job fails via the check above, so 40 and 45 are the warnings.
314
+ if (DEFERRAL_WARN_MILESTONES.includes(deferrals)) {
298
315
  log.warn(
299
316
  { jobId: id, type: row.type, deferrals, max: MAX_DEFERRALS },
300
317
  "Job approaching max deferral limit",
@@ -3,7 +3,6 @@ import type { AssistantConfig } from "../config/types.js";
3
3
  import { getLogger } from "../util/logger.js";
4
4
  import { rawRun } from "./db.js";
5
5
  import { backfillJob } from "./job-handlers/backfill.js";
6
- import { backfillSimplifiedMemoryJob } from "./job-handlers/backfill-simplified-memory.js";
7
6
  import {
8
7
  cleanupStaleSupersededItemsJob,
9
8
  pruneOldConversationsJob,
@@ -12,11 +11,8 @@ import { generateConversationStartersJob } from "./job-handlers/conversation-sta
12
11
  // ── Per-job-type handlers ──────────────────────────────────────────
13
12
  import {
14
13
  embedAttachmentJob,
15
- embedChunkJob,
16
- embedEpisodeJob,
17
14
  embedItemJob,
18
15
  embedMediaJob,
19
- embedObservationJob,
20
16
  embedSegmentJob,
21
17
  embedSummaryJob,
22
18
  } from "./job-handlers/embedding.js";
@@ -26,7 +22,6 @@ import {
26
22
  rebuildIndexJob,
27
23
  } from "./job-handlers/index-maintenance.js";
28
24
  import { mediaProcessingJob } from "./job-handlers/media-processing.js";
29
- import { reduceConversationMemoryJob } from "./job-handlers/reduce-conversation-memory.js";
30
25
  import { buildConversationSummaryJob } from "./job-handlers/summarization.js";
31
26
  import {
32
27
  BackendUnavailableError,
@@ -49,6 +44,9 @@ import { QdrantCircuitOpenError } from "./qdrant-circuit-breaker.js";
49
44
 
50
45
  const log = getLogger("memory-jobs-worker");
51
46
 
47
+ export const POLL_INTERVAL_MIN_MS = 1_500;
48
+ export const POLL_INTERVAL_MAX_MS = 30_000;
49
+
52
50
  export interface MemoryJobsWorker {
53
51
  runOnce(): Promise<number>;
54
52
  stop(): void;
@@ -62,24 +60,45 @@ export function startMemoryJobsWorker(): MemoryJobsWorker {
62
60
 
63
61
  let stopped = false;
64
62
  let tickRunning = false;
63
+ let timer: ReturnType<typeof setTimeout>;
64
+ let currentIntervalMs = POLL_INTERVAL_MIN_MS;
65
65
 
66
66
  const tick = async () => {
67
67
  if (stopped || tickRunning) return;
68
68
  tickRunning = true;
69
69
  try {
70
- await runMemoryJobsOnce({ enableScheduledCleanup: true });
70
+ const processed = await runMemoryJobsOnce({
71
+ enableScheduledCleanup: true,
72
+ });
73
+ if (processed > 0) {
74
+ currentIntervalMs = POLL_INTERVAL_MIN_MS;
75
+ } else {
76
+ currentIntervalMs = Math.min(
77
+ currentIntervalMs * 2,
78
+ POLL_INTERVAL_MAX_MS,
79
+ );
80
+ }
71
81
  } catch (err) {
72
82
  log.error({ err }, "Memory worker tick failed");
83
+ currentIntervalMs = Math.min(currentIntervalMs * 2, POLL_INTERVAL_MAX_MS);
73
84
  } finally {
74
85
  tickRunning = false;
75
86
  }
76
87
  };
77
88
 
78
- const timer = setInterval(() => {
79
- void tick();
80
- }, 1500);
81
- timer.unref();
82
- void tick();
89
+ const scheduleTick = () => {
90
+ if (stopped) return;
91
+ timer = setTimeout(() => {
92
+ void tick().then(() => {
93
+ if (!stopped) scheduleTick();
94
+ });
95
+ }, currentIntervalMs);
96
+ (timer as NodeJS.Timeout).unref?.();
97
+ };
98
+
99
+ void tick().then(() => {
100
+ if (!stopped) scheduleTick();
101
+ });
83
102
 
84
103
  return {
85
104
  async runOnce(): Promise<number> {
@@ -87,7 +106,7 @@ export function startMemoryJobsWorker(): MemoryJobsWorker {
87
106
  },
88
107
  stop(): void {
89
108
  stopped = true;
90
- clearInterval(timer);
109
+ clearTimeout(timer);
91
110
  },
92
111
  };
93
112
  }
@@ -272,15 +291,6 @@ async function processJob(
272
291
  case "embed_summary":
273
292
  await embedSummaryJob(job, config);
274
293
  return;
275
- case "embed_chunk":
276
- await embedChunkJob(job, config);
277
- return;
278
- case "embed_episode":
279
- await embedEpisodeJob(job, config);
280
- return;
281
- case "embed_observation":
282
- await embedObservationJob(job, config);
283
- return;
284
294
  case "extract_items":
285
295
  await extractItemsJob(job);
286
296
  return;
@@ -321,12 +331,6 @@ async function processJob(
321
331
  case "embed_attachment":
322
332
  await embedAttachmentJob(job, config);
323
333
  return;
324
- case "reduce_conversation_memory":
325
- await reduceConversationMemoryJob(job);
326
- return;
327
- case "backfill_simplified_memory":
328
- await backfillSimplifiedMemoryJob(job);
329
- return;
330
334
  case "generate_conversation_starters":
331
335
  await generateConversationStartersJob(job);
332
336
  return;
@@ -49,3 +49,22 @@ export function migrateJobDeferrals(database: DrizzleDb): void {
49
49
  throw e;
50
50
  }
51
51
  }
52
+
53
+ /**
54
+ * Reverse the deferral reconciliation by moving `deferrals` back into `attempts`
55
+ * for pending embed jobs. Best-effort: jobs that accumulated real deferral counts
56
+ * after the forward migration ran cannot be distinguished from migrated ones.
57
+ */
58
+ export function downJobDeferrals(database: DrizzleDb): void {
59
+ const raw = getSqliteFrom(database);
60
+ raw.exec(/*sql*/ `
61
+ UPDATE memory_jobs
62
+ SET attempts = deferrals,
63
+ deferrals = 0,
64
+ updated_at = ${Date.now()}
65
+ WHERE status = 'pending'
66
+ AND deferrals > 0
67
+ AND attempts = 0
68
+ AND type IN ('embed_segment', 'embed_item', 'embed_summary')
69
+ `);
70
+ }
@@ -91,3 +91,13 @@ export function migrateMemoryEntityRelationDedup(database: DrizzleDb): void {
91
91
  throw e;
92
92
  }
93
93
  }
94
+
95
+ /**
96
+ * No-op down: deduplication is a lossy operation — deleted duplicate rows
97
+ * cannot be restored. The forward migration merged rows by keeping the most
98
+ * recent evidence per (source, target, relation) triple; the discarded rows
99
+ * are permanently lost.
100
+ */
101
+ export function downMemoryEntityRelationDedup(_database: DrizzleDb): void {
102
+ // Intentionally empty — irreversible lossy migration.
103
+ }
@@ -93,3 +93,79 @@ export function migrateMemoryItemsFingerprintScopeUnique(
93
93
  raw.exec("PRAGMA foreign_keys = ON");
94
94
  }
95
95
  }
96
+
97
+ /**
98
+ * Reverse the compound (fingerprint, scope_id) unique index change by rebuilding
99
+ * memory_items with a column-level UNIQUE on fingerprint.
100
+ *
101
+ * WARNING: This is dangerous if data now relies on the compound constraint
102
+ * (i.e., the same fingerprint exists in multiple scopes). In that case, the
103
+ * rebuild will fail with a UNIQUE constraint violation. This is intentional —
104
+ * it prevents silent data loss on rollback.
105
+ */
106
+ export function downMemoryItemsFingerprintScopeUnique(
107
+ database: DrizzleDb,
108
+ ): void {
109
+ const raw = getSqliteFrom(database);
110
+
111
+ // Check if the column-level UNIQUE already exists — if so, nothing to do.
112
+ const tableDdl = raw
113
+ .query(
114
+ `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'memory_items'`,
115
+ )
116
+ .get() as { sql: string } | null;
117
+ if (
118
+ !tableDdl ||
119
+ tableDdl.sql.match(/fingerprint\s+TEXT\s+NOT\s+NULL\s+UNIQUE/i)
120
+ ) {
121
+ return;
122
+ }
123
+
124
+ raw.exec("PRAGMA foreign_keys = OFF");
125
+ try {
126
+ raw.exec("BEGIN");
127
+
128
+ raw.exec(/*sql*/ `
129
+ CREATE TABLE memory_items_new (
130
+ id TEXT PRIMARY KEY,
131
+ kind TEXT NOT NULL,
132
+ subject TEXT NOT NULL,
133
+ statement TEXT NOT NULL,
134
+ status TEXT NOT NULL,
135
+ confidence REAL NOT NULL,
136
+ fingerprint TEXT NOT NULL UNIQUE,
137
+ first_seen_at INTEGER NOT NULL,
138
+ last_seen_at INTEGER NOT NULL,
139
+ last_used_at INTEGER,
140
+ importance REAL,
141
+ access_count INTEGER NOT NULL DEFAULT 0,
142
+ valid_from INTEGER,
143
+ invalid_at INTEGER,
144
+ verification_state TEXT NOT NULL DEFAULT 'assistant_inferred',
145
+ scope_id TEXT NOT NULL DEFAULT 'default'
146
+ )
147
+ `);
148
+
149
+ raw.exec(/*sql*/ `
150
+ INSERT INTO memory_items_new
151
+ SELECT id, kind, subject, statement, status, confidence, fingerprint,
152
+ first_seen_at, last_seen_at, last_used_at, importance, access_count,
153
+ valid_from, invalid_at, verification_state, scope_id
154
+ FROM memory_items
155
+ `);
156
+
157
+ raw.exec(/*sql*/ `DROP TABLE memory_items`);
158
+ raw.exec(/*sql*/ `ALTER TABLE memory_items_new RENAME TO memory_items`);
159
+
160
+ raw.exec("COMMIT");
161
+ } catch (e) {
162
+ try {
163
+ raw.exec("ROLLBACK");
164
+ } catch {
165
+ /* no active transaction */
166
+ }
167
+ throw e;
168
+ } finally {
169
+ raw.exec("PRAGMA foreign_keys = ON");
170
+ }
171
+ }
@@ -1,3 +1,5 @@
1
+ import { createHash } from "node:crypto";
2
+
1
3
  import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
2
4
  import { computeMemoryFingerprint } from "../fingerprint.js";
3
5
 
@@ -75,3 +77,51 @@ export function migrateMemoryItemsScopeSaltedFingerprints(
75
77
  throw e;
76
78
  }
77
79
  }
80
+
81
+ /**
82
+ * Reverse the scope-salted fingerprint migration by recomputing fingerprints
83
+ * WITHOUT the scope_id prefix.
84
+ *
85
+ * Old format: sha256(`${kind}|${subject.toLowerCase()}|${statement.toLowerCase()}`)
86
+ */
87
+ export function downMemoryItemsScopeSaltedFingerprints(
88
+ database: DrizzleDb,
89
+ ): void {
90
+ const raw = getSqliteFrom(database);
91
+
92
+ interface ItemRow {
93
+ id: string;
94
+ kind: string;
95
+ subject: string;
96
+ statement: string;
97
+ }
98
+
99
+ const items = raw
100
+ .query(`SELECT id, kind, subject, statement FROM memory_items`)
101
+ .all() as ItemRow[];
102
+
103
+ if (items.length === 0) return;
104
+
105
+ try {
106
+ raw.exec("BEGIN");
107
+
108
+ const updateStmt = raw.prepare(
109
+ `UPDATE memory_items SET fingerprint = ? WHERE id = ?`,
110
+ );
111
+
112
+ for (const item of items) {
113
+ const normalized = `${item.kind}|${item.subject.toLowerCase()}|${item.statement.toLowerCase()}`;
114
+ const fingerprint = createHash("sha256").update(normalized).digest("hex");
115
+ updateStmt.run(fingerprint, item.id);
116
+ }
117
+
118
+ raw.exec("COMMIT");
119
+ } catch (e) {
120
+ try {
121
+ raw.exec("ROLLBACK");
122
+ } catch {
123
+ /* no active transaction */
124
+ }
125
+ throw e;
126
+ }
127
+ }
@@ -265,3 +265,13 @@ export function migrateAssistantIdToSelf(database: DrizzleDb): void {
265
265
  throw e;
266
266
  }
267
267
  }
268
+
269
+ /**
270
+ * No-op down: the original assistant_id values are not recoverable. The forward
271
+ * migration normalized all assistant_id values to "self" and merged/deduplicated
272
+ * rows where the same logical entity existed under both the real assistantId and
273
+ * "self". The original per-assistant IDs are permanently lost.
274
+ */
275
+ export function downAssistantIdToSelf(_database: DrizzleDb): void {
276
+ // Intentionally empty — original assistant_id values cannot be restored.
277
+ }
@@ -228,3 +228,37 @@ export function migrateRemoveAssistantIdColumns(database: DrizzleDb): void {
228
228
  raw.exec("PRAGMA foreign_keys = ON");
229
229
  }
230
230
  }
231
+
232
+ /**
233
+ * Add the assistant_id column back to the 4 tables that had it removed.
234
+ *
235
+ * NOTE: The data previously stored in assistant_id is lost — all rows will
236
+ * have assistant_id = 'self' after this down migration. This only restores
237
+ * the column structure so that older code expecting the column can function.
238
+ */
239
+ export function downRemoveAssistantIdColumns(database: DrizzleDb): void {
240
+ const raw = getSqliteFrom(database);
241
+
242
+ const tables = [
243
+ "conversation_keys",
244
+ "attachments",
245
+ "channel_inbound_events",
246
+ "message_runs",
247
+ ];
248
+
249
+ for (const table of tables) {
250
+ // Check if the table exists and lacks assistant_id
251
+ const ddl = raw
252
+ .query(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`)
253
+ .get(table) as { sql: string } | null;
254
+ if (!ddl || ddl.sql.includes("assistant_id")) continue;
255
+
256
+ try {
257
+ raw.exec(
258
+ /*sql*/ `ALTER TABLE ${table} ADD COLUMN assistant_id TEXT NOT NULL DEFAULT 'self'`,
259
+ );
260
+ } catch {
261
+ /* column already exists */
262
+ }
263
+ }
264
+ }
@@ -95,3 +95,29 @@ export function migrateLlmUsageEventsDropAssistantId(
95
95
  raw.exec("PRAGMA foreign_keys = ON");
96
96
  }
97
97
  }
98
+
99
+ /**
100
+ * Add the assistant_id column back to llm_usage_events.
101
+ *
102
+ * NOTE: The data previously stored in assistant_id is lost — all rows will
103
+ * have assistant_id = NULL after this down migration. This only restores
104
+ * the column structure so that older code expecting the column can function.
105
+ */
106
+ export function downLlmUsageEventsDropAssistantId(database: DrizzleDb): void {
107
+ const raw = getSqliteFrom(database);
108
+
109
+ const ddl = raw
110
+ .query(
111
+ `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'llm_usage_events'`,
112
+ )
113
+ .get() as { sql: string } | null;
114
+ if (!ddl || ddl.sql.includes("assistant_id")) return;
115
+
116
+ try {
117
+ raw.exec(
118
+ /*sql*/ `ALTER TABLE llm_usage_events ADD COLUMN assistant_id TEXT`,
119
+ );
120
+ } catch {
121
+ /* column already exists */
122
+ }
123
+ }
@@ -88,3 +88,13 @@ export function migrateBackfillInboxThreadStateFromBindings(
88
88
  throw e;
89
89
  }
90
90
  }
91
+
92
+ /**
93
+ * No-op down: the seeded inbox thread state rows are expected to remain.
94
+ * The forward migration used INSERT OR IGNORE, so existing rows were never
95
+ * modified. Removing the seeded rows could leave the inbox empty for
96
+ * pre-existing conversations, which is worse than keeping them.
97
+ */
98
+ export function downBackfillInboxThreadState(_database: DrizzleDb): void {
99
+ // Intentionally empty — seeded data is expected to remain.
100
+ }
@@ -31,3 +31,20 @@ export function migrateDropActiveSearchIndex(database: DrizzleDb): void {
31
31
  throw e;
32
32
  }
33
33
  }
34
+
35
+ /**
36
+ * Recreate the old idx_memory_items_active_search index with its original
37
+ * covering columns (before the migration added status and invalid_at as
38
+ * indexed columns).
39
+ */
40
+ export function downDropActiveSearchIndex(database: DrizzleDb): void {
41
+ const raw = getSqliteFrom(database);
42
+
43
+ // Drop the current index if it exists, then recreate with the old column set.
44
+ raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_memory_items_active_search`);
45
+ raw.exec(/*sql*/ `
46
+ CREATE INDEX IF NOT EXISTS idx_memory_items_active_search
47
+ ON memory_items(last_seen_at DESC, subject, statement, id, kind, confidence, importance, first_seen_at, scope_id)
48
+ WHERE status = 'active' AND invalid_at IS NULL
49
+ `);
50
+ }
@@ -82,3 +82,15 @@ export function migrateNotificationTablesSchema(database: DrizzleDb): void {
82
82
  }
83
83
  }
84
84
  }
85
+
86
+ /**
87
+ * No-op down: the old enum-based notification tables cannot be recreated
88
+ * without the original schema definitions (column names, types, constraints,
89
+ * and enum values). The forward migration dropped these tables entirely.
90
+ * Any data that was in them is permanently lost. The new signal-contract
91
+ * schema tables are structurally incompatible with the old enum-based ones.
92
+ */
93
+ export function downNotificationTablesSchema(_database: DrizzleDb): void {
94
+ // Intentionally empty — old enum-based tables cannot be recreated without
95
+ // the original schema, and any data they contained is permanently lost.
96
+ }
@@ -130,3 +130,124 @@ export function migrateRenameChannelToVellum(database: DrizzleDb): void {
130
130
  throw e;
131
131
  }
132
132
  }
133
+
134
+ /**
135
+ * Reverse the channel rename by changing "vellum" back to "macos" in all tables.
136
+ *
137
+ * NOTE: The forward migration renamed both "macos" and "ios" to "vellum", so we
138
+ * cannot distinguish which rows were originally "ios". This down migration
139
+ * conservatively maps all "vellum" values back to "macos" since that was the
140
+ * primary desktop channel identifier.
141
+ */
142
+ export function downRenameChannelToVellum(database: DrizzleDb): void {
143
+ const raw = getSqliteFrom(database);
144
+
145
+ try {
146
+ raw.exec("BEGIN");
147
+
148
+ // guardian_action_deliveries.destination_channel
149
+ const gadExists = raw
150
+ .query(
151
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'guardian_action_deliveries'`,
152
+ )
153
+ .get();
154
+ if (gadExists) {
155
+ raw
156
+ .query(
157
+ `UPDATE guardian_action_deliveries SET destination_channel = 'macos' WHERE destination_channel = 'vellum'`,
158
+ )
159
+ .run();
160
+ }
161
+
162
+ // messages.user_message_channel / assistant_message_channel
163
+ const msgsExists = raw
164
+ .query(
165
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'messages'`,
166
+ )
167
+ .get();
168
+ if (msgsExists) {
169
+ const hasUserMsgChannel = raw
170
+ .query(
171
+ `SELECT 1 FROM pragma_table_info('messages') WHERE name = 'user_message_channel'`,
172
+ )
173
+ .get();
174
+ if (hasUserMsgChannel) {
175
+ raw
176
+ .query(
177
+ `UPDATE messages SET user_message_channel = 'macos' WHERE user_message_channel = 'vellum'`,
178
+ )
179
+ .run();
180
+ }
181
+ const hasAssistantMsgChannel = raw
182
+ .query(
183
+ `SELECT 1 FROM pragma_table_info('messages') WHERE name = 'assistant_message_channel'`,
184
+ )
185
+ .get();
186
+ if (hasAssistantMsgChannel) {
187
+ raw
188
+ .query(
189
+ `UPDATE messages SET assistant_message_channel = 'macos' WHERE assistant_message_channel = 'vellum'`,
190
+ )
191
+ .run();
192
+ }
193
+ }
194
+
195
+ // external_conversation_bindings.source_channel
196
+ const ecbExists = raw
197
+ .query(
198
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'external_conversation_bindings'`,
199
+ )
200
+ .get();
201
+ if (ecbExists) {
202
+ raw
203
+ .query(
204
+ `UPDATE external_conversation_bindings SET source_channel = 'macos' WHERE source_channel = 'vellum'`,
205
+ )
206
+ .run();
207
+ }
208
+
209
+ // assistant_inbox_thread_state.source_channel
210
+ const aitsExists = raw
211
+ .query(
212
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'assistant_inbox_thread_state'`,
213
+ )
214
+ .get();
215
+ if (aitsExists) {
216
+ raw
217
+ .query(
218
+ `UPDATE assistant_inbox_thread_state SET source_channel = 'macos' WHERE source_channel = 'vellum'`,
219
+ )
220
+ .run();
221
+ }
222
+
223
+ // conversations.origin_channel
224
+ const convExists = raw
225
+ .query(
226
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'conversations'`,
227
+ )
228
+ .get();
229
+ if (convExists) {
230
+ const hasOriginChannel = raw
231
+ .query(
232
+ `SELECT 1 FROM pragma_table_info('conversations') WHERE name = 'origin_channel'`,
233
+ )
234
+ .get();
235
+ if (hasOriginChannel) {
236
+ raw
237
+ .query(
238
+ `UPDATE conversations SET origin_channel = 'macos' WHERE origin_channel = 'vellum'`,
239
+ )
240
+ .run();
241
+ }
242
+ }
243
+
244
+ raw.exec("COMMIT");
245
+ } catch (e) {
246
+ try {
247
+ raw.exec("ROLLBACK");
248
+ } catch {
249
+ /* no active transaction */
250
+ }
251
+ throw e;
252
+ }
253
+ }