@vellumai/assistant 0.5.6 → 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 (305) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +1 -1
  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 +0 -114
  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 +1 -1
  26. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  27. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  28. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  29. package/src/__tests__/conversation-title-service.test.ts +87 -0
  30. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  31. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  32. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  33. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  34. package/src/__tests__/credentials-cli.test.ts +78 -0
  35. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  36. package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
  37. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  38. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  39. package/src/__tests__/host-shell-tool.test.ts +6 -7
  40. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  41. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  42. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  43. package/src/__tests__/intent-routing.test.ts +0 -13
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  45. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  46. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  47. package/src/__tests__/migration-export-http.test.ts +2 -2
  48. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  49. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  50. package/src/__tests__/migration-validate-http.test.ts +2 -2
  51. package/src/__tests__/non-member-access-request.test.ts +0 -5
  52. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  53. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  54. package/src/__tests__/permission-types.test.ts +1 -0
  55. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  56. package/src/__tests__/qdrant-manager.test.ts +28 -2
  57. package/src/__tests__/registry.test.ts +0 -6
  58. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  59. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  60. package/src/__tests__/secure-keys.test.ts +83 -263
  61. package/src/__tests__/shell-identity.test.ts +96 -6
  62. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  63. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  64. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  65. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  66. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  67. package/src/__tests__/skill-load-tool.test.ts +0 -2
  68. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  69. package/src/__tests__/skills.test.ts +0 -2
  70. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  71. package/src/__tests__/suggestion-routes.test.ts +1 -32
  72. package/src/__tests__/system-prompt.test.ts +0 -1
  73. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  74. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  75. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  76. package/src/__tests__/update-bulletin.test.ts +0 -2
  77. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  78. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  79. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  80. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  81. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  82. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  83. package/src/calls/audio-store.test.ts +97 -0
  84. package/src/calls/audio-store.ts +205 -0
  85. package/src/calls/call-controller.ts +85 -7
  86. package/src/calls/call-domain.ts +3 -0
  87. package/src/calls/call-store.ts +10 -3
  88. package/src/calls/fish-audio-client.ts +117 -0
  89. package/src/calls/relay-server.ts +27 -0
  90. package/src/calls/twilio-routes.ts +2 -1
  91. package/src/calls/types.ts +1 -0
  92. package/src/calls/voice-ingress-preflight.ts +0 -42
  93. package/src/calls/voice-quality.ts +26 -5
  94. package/src/calls/voice-session-bridge.ts +6 -12
  95. package/src/cli/commands/config.ts +1 -4
  96. package/src/cli/commands/credentials.ts +34 -4
  97. package/src/cli/commands/oauth/index.ts +7 -0
  98. package/src/cli/commands/oauth/platform.ts +179 -0
  99. package/src/cli/commands/platform.ts +3 -3
  100. package/src/config/assistant-feature-flags.ts +186 -5
  101. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  102. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  103. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  104. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  105. package/src/config/bundled-tool-registry.ts +1 -11
  106. package/src/config/env-registry.ts +1 -1
  107. package/src/config/env.ts +8 -14
  108. package/src/config/feature-flag-registry.json +48 -8
  109. package/src/config/loader.ts +98 -31
  110. package/src/config/schema.ts +4 -13
  111. package/src/config/schemas/calls.ts +13 -0
  112. package/src/config/schemas/fish-audio.ts +39 -0
  113. package/src/config/schemas/security.ts +0 -4
  114. package/src/config/types.ts +0 -1
  115. package/src/contacts/contact-store.ts +39 -0
  116. package/src/contacts/types.ts +2 -0
  117. package/src/credential-execution/approval-bridge.ts +1 -0
  118. package/src/credential-execution/executable-discovery.ts +28 -4
  119. package/src/credential-execution/feature-gates.ts +16 -0
  120. package/src/credential-execution/process-manager.ts +38 -0
  121. package/src/daemon/assistant-attachments.ts +9 -0
  122. package/src/daemon/config-watcher.ts +5 -0
  123. package/src/daemon/conversation-tool-setup.ts +0 -105
  124. package/src/daemon/conversation.ts +10 -1
  125. package/src/daemon/handlers/config-vercel.ts +92 -0
  126. package/src/daemon/handlers/skills.ts +2 -15
  127. package/src/daemon/install-symlink.ts +195 -0
  128. package/src/daemon/lifecycle.ts +227 -51
  129. package/src/daemon/message-types/conversations.ts +3 -4
  130. package/src/daemon/message-types/diagnostics.ts +3 -22
  131. package/src/daemon/message-types/messages.ts +0 -2
  132. package/src/daemon/message-types/upgrades.ts +8 -0
  133. package/src/daemon/server.ts +30 -92
  134. package/src/events/domain-events.ts +2 -1
  135. package/src/inbound/platform-callback-registration.ts +3 -3
  136. package/src/instrument.ts +8 -5
  137. package/src/memory/conversation-title-service.ts +50 -1
  138. package/src/memory/db-init.ts +12 -0
  139. package/src/memory/items-extractor.ts +15 -1
  140. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  141. package/src/memory/jobs-store.ts +30 -5
  142. package/src/memory/jobs-worker.ts +31 -7
  143. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  144. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  145. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  146. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  147. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  148. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  149. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  150. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  151. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  152. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  153. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  154. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  155. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  156. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  157. package/src/memory/migrations/116-messages-fts.ts +106 -1
  158. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  159. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  160. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  161. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  162. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  163. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  164. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  165. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  166. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  167. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  168. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  169. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  170. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  171. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  172. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  173. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  174. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  175. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  176. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  177. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  178. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  179. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  180. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  181. package/src/memory/migrations/index.ts +4 -0
  182. package/src/memory/migrations/registry.ts +90 -0
  183. package/src/memory/migrations/validate-migration-state.ts +137 -11
  184. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  185. package/src/memory/qdrant-manager.ts +64 -7
  186. package/src/memory/schema/calls.ts +1 -0
  187. package/src/memory/schema/contacts.ts +1 -0
  188. package/src/notifications/decision-engine.ts +4 -1
  189. package/src/oauth/connection-resolver.ts +6 -4
  190. package/src/permissions/checker.ts +0 -38
  191. package/src/permissions/shell-identity.ts +76 -22
  192. package/src/permissions/types.ts +4 -2
  193. package/src/platform/client.ts +35 -7
  194. package/src/prompts/persona-resolver.ts +138 -0
  195. package/src/prompts/system-prompt.ts +36 -4
  196. package/src/prompts/templates/users/default.md +1 -0
  197. package/src/providers/registry.ts +27 -40
  198. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  199. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  200. package/src/runtime/auth/external-assistant-id.ts +13 -59
  201. package/src/runtime/auth/route-policy.ts +15 -1
  202. package/src/runtime/auth/token-service.ts +43 -138
  203. package/src/runtime/channel-readiness-service.ts +1 -16
  204. package/src/runtime/http-server.ts +27 -2
  205. package/src/runtime/middleware/error-handler.ts +1 -9
  206. package/src/runtime/routes/audio-routes.ts +40 -0
  207. package/src/runtime/routes/btw-routes.ts +0 -17
  208. package/src/runtime/routes/conversation-query-routes.ts +63 -1
  209. package/src/runtime/routes/conversation-routes.ts +4 -44
  210. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  211. package/src/runtime/routes/identity-routes.ts +18 -29
  212. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  213. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  214. package/src/runtime/routes/integrations/vercel.ts +89 -0
  215. package/src/runtime/routes/log-export-routes.ts +5 -0
  216. package/src/runtime/routes/memory-item-routes.ts +24 -6
  217. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  218. package/src/runtime/routes/migration-routes.ts +17 -1
  219. package/src/runtime/routes/notification-routes.ts +58 -0
  220. package/src/runtime/routes/schedule-routes.ts +65 -0
  221. package/src/runtime/routes/settings-routes.ts +41 -1
  222. package/src/runtime/routes/tts-routes.ts +86 -0
  223. package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
  224. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  225. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  226. package/src/runtime/routes/workspace-routes.ts +1 -1
  227. package/src/runtime/routes/workspace-utils.ts +86 -2
  228. package/src/security/ces-credential-client.ts +59 -22
  229. package/src/security/ces-rpc-credential-backend.ts +85 -0
  230. package/src/security/credential-backend.ts +12 -88
  231. package/src/security/keychain-broker-client.ts +10 -2
  232. package/src/security/secure-keys.ts +94 -113
  233. package/src/skills/catalog-install.ts +13 -7
  234. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  235. package/src/tools/calls/call-start.ts +1 -0
  236. package/src/tools/executor.ts +0 -4
  237. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  238. package/src/tools/network/web-fetch.ts +3 -1
  239. package/src/tools/skills/execute.ts +1 -1
  240. package/src/tools/types.ts +0 -8
  241. package/src/util/errors.ts +0 -12
  242. package/src/util/platform.ts +3 -50
  243. package/src/workspace/git-service.ts +5 -2
  244. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  245. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  246. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  247. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  248. package/src/workspace/migrations/006-services-config.ts +49 -0
  249. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  250. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  251. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  252. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  253. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  254. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  255. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  256. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  257. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  258. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  259. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  260. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  261. package/src/workspace/migrations/registry.ts +8 -0
  262. package/src/workspace/migrations/runner.ts +106 -2
  263. package/src/workspace/migrations/types.ts +4 -0
  264. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  265. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  266. package/src/__tests__/diagnostics-export.test.ts +0 -288
  267. package/src/__tests__/local-gateway-health.test.ts +0 -209
  268. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  269. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  270. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  271. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  272. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  273. package/src/__tests__/swarm-recursion.test.ts +0 -197
  274. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  275. package/src/__tests__/swarm-tool.test.ts +0 -185
  276. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  277. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  278. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  279. package/src/commands/cc-command-registry.ts +0 -248
  280. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  281. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  282. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  283. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  284. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  285. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  286. package/src/config/schemas/swarm.ts +0 -82
  287. package/src/logfire.ts +0 -135
  288. package/src/runtime/local-gateway-health.ts +0 -275
  289. package/src/security/secret-ingress.ts +0 -68
  290. package/src/swarm/backend-claude-code.ts +0 -225
  291. package/src/swarm/checkpoint.ts +0 -137
  292. package/src/swarm/graph-utils.ts +0 -53
  293. package/src/swarm/index.ts +0 -55
  294. package/src/swarm/limits.ts +0 -66
  295. package/src/swarm/orchestrator.ts +0 -424
  296. package/src/swarm/plan-validator.ts +0 -117
  297. package/src/swarm/router-planner.ts +0 -162
  298. package/src/swarm/router-prompts.ts +0 -39
  299. package/src/swarm/synthesizer.ts +0 -81
  300. package/src/swarm/types.ts +0 -72
  301. package/src/swarm/worker-backend.ts +0 -131
  302. package/src/swarm/worker-prompts.ts +0 -80
  303. package/src/swarm/worker-runner.ts +0 -170
  304. package/src/tools/claude-code/claude-code.ts +0 -610
  305. package/src/tools/swarm/delegate.ts +0 -205
@@ -281,3 +281,293 @@ function rebuildScopedApprovalGrants(raw: RawDb): void {
281
281
  raw.exec("PRAGMA foreign_keys = ON");
282
282
  }
283
283
  }
284
+
285
+ // ---------------------------------------------------------------------------
286
+ // Down functions
287
+ // ---------------------------------------------------------------------------
288
+
289
+ /**
290
+ * Reverse v29: convert epoch ms timestamps back to ISO 8601 text in guardian
291
+ * tables.
292
+ *
293
+ * Uses SQLite's datetime() to reconstruct ISO 8601 strings from the integer
294
+ * values. The millisecond component is appended manually since datetime()
295
+ * only returns second precision.
296
+ */
297
+ export function migrateGuardianTimestampsEpochMsDown(
298
+ database: DrizzleDb,
299
+ ): void {
300
+ const raw = getSqliteFrom(database);
301
+
302
+ // Convert canonical_guardian_requests timestamp columns back to ISO 8601
303
+ raw.exec(/*sql*/ `
304
+ UPDATE canonical_guardian_requests
305
+ SET created_at = strftime('%Y-%m-%dT%H:%M:%S', created_at / 1000, 'unixepoch') || '.' || printf('%03d', created_at % 1000) || 'Z',
306
+ updated_at = strftime('%Y-%m-%dT%H:%M:%S', updated_at / 1000, 'unixepoch') || '.' || printf('%03d', updated_at % 1000) || 'Z',
307
+ expires_at = CASE
308
+ WHEN expires_at IS NOT NULL
309
+ THEN strftime('%Y-%m-%dT%H:%M:%S', expires_at / 1000, 'unixepoch') || '.' || printf('%03d', expires_at % 1000) || 'Z'
310
+ ELSE NULL
311
+ END
312
+ WHERE typeof(created_at) = 'integer'
313
+ `);
314
+
315
+ // Convert canonical_guardian_deliveries timestamp columns back to ISO 8601
316
+ raw.exec(/*sql*/ `
317
+ UPDATE canonical_guardian_deliveries
318
+ SET created_at = strftime('%Y-%m-%dT%H:%M:%S', created_at / 1000, 'unixepoch') || '.' || printf('%03d', created_at % 1000) || 'Z',
319
+ updated_at = strftime('%Y-%m-%dT%H:%M:%S', updated_at / 1000, 'unixepoch') || '.' || printf('%03d', updated_at % 1000) || 'Z'
320
+ WHERE typeof(created_at) = 'integer'
321
+ `);
322
+
323
+ // Convert scoped_approval_grants timestamp columns back to ISO 8601
324
+ raw.exec(/*sql*/ `
325
+ UPDATE scoped_approval_grants
326
+ SET expires_at = strftime('%Y-%m-%dT%H:%M:%S', expires_at / 1000, 'unixepoch') || '.' || printf('%03d', expires_at % 1000) || 'Z',
327
+ created_at = strftime('%Y-%m-%dT%H:%M:%S', created_at / 1000, 'unixepoch') || '.' || printf('%03d', created_at % 1000) || 'Z',
328
+ updated_at = strftime('%Y-%m-%dT%H:%M:%S', updated_at / 1000, 'unixepoch') || '.' || printf('%03d', updated_at % 1000) || 'Z',
329
+ consumed_at = CASE
330
+ WHEN consumed_at IS NOT NULL
331
+ THEN strftime('%Y-%m-%dT%H:%M:%S', consumed_at / 1000, 'unixepoch') || '.' || printf('%03d', consumed_at % 1000) || 'Z'
332
+ ELSE NULL
333
+ END
334
+ WHERE typeof(created_at) = 'integer'
335
+ `);
336
+ }
337
+
338
+ /**
339
+ * Reverse v30: rebuild guardian tables with TEXT affinity on timestamp columns.
340
+ *
341
+ * Restores the original TEXT column declarations so that timestamp columns
342
+ * have TEXT affinity (the state before the INTEGER rebuild).
343
+ */
344
+ export function migrateGuardianTimestampsRebuildDown(
345
+ database: DrizzleDb,
346
+ ): void {
347
+ const raw = getSqliteFrom(database);
348
+
349
+ rebuildCanonicalGuardianRequestsToText(raw);
350
+ rebuildCanonicalGuardianDeliveriesToText(raw);
351
+ rebuildScopedApprovalGrantsToText(raw);
352
+ }
353
+
354
+ function hasTextAffinity(raw: RawDb, table: string, column: string): boolean {
355
+ const row = raw
356
+ .query(
357
+ `SELECT type FROM pragma_table_info('${table}') WHERE name = '${column}'`,
358
+ )
359
+ .get() as { type: string } | null;
360
+ if (!row) return true; // column doesn't exist — nothing to fix
361
+ return row.type.toUpperCase() === "TEXT";
362
+ }
363
+
364
+ function rebuildCanonicalGuardianRequestsToText(raw: RawDb): void {
365
+ if (hasTextAffinity(raw, "canonical_guardian_requests", "created_at")) return;
366
+
367
+ raw.exec("PRAGMA foreign_keys = OFF");
368
+ try {
369
+ raw.exec("BEGIN");
370
+
371
+ raw.exec(/*sql*/ `
372
+ CREATE TABLE canonical_guardian_requests_rb (
373
+ id TEXT PRIMARY KEY,
374
+ kind TEXT NOT NULL,
375
+ source_type TEXT NOT NULL,
376
+ source_channel TEXT,
377
+ conversation_id TEXT,
378
+ requester_external_user_id TEXT,
379
+ requester_chat_id TEXT,
380
+ guardian_external_user_id TEXT,
381
+ guardian_principal_id TEXT,
382
+ call_session_id TEXT,
383
+ pending_question_id TEXT,
384
+ question_text TEXT,
385
+ request_code TEXT,
386
+ tool_name TEXT,
387
+ input_digest TEXT,
388
+ status TEXT NOT NULL DEFAULT 'pending',
389
+ answer_text TEXT,
390
+ decided_by_external_user_id TEXT,
391
+ decided_by_principal_id TEXT,
392
+ followup_state TEXT,
393
+ expires_at TEXT,
394
+ created_at TEXT NOT NULL,
395
+ updated_at TEXT NOT NULL
396
+ )
397
+ `);
398
+
399
+ raw.exec(/*sql*/ `
400
+ INSERT INTO canonical_guardian_requests_rb
401
+ SELECT id, kind, source_type, source_channel, conversation_id,
402
+ requester_external_user_id, requester_chat_id,
403
+ guardian_external_user_id, guardian_principal_id,
404
+ call_session_id, pending_question_id, question_text,
405
+ request_code, tool_name, input_digest, status, answer_text,
406
+ decided_by_external_user_id, decided_by_principal_id,
407
+ followup_state, expires_at, created_at, updated_at
408
+ FROM canonical_guardian_requests
409
+ `);
410
+
411
+ raw.exec(/*sql*/ `DROP TABLE canonical_guardian_requests`);
412
+ raw.exec(
413
+ /*sql*/ `ALTER TABLE canonical_guardian_requests_rb RENAME TO canonical_guardian_requests`,
414
+ );
415
+
416
+ raw.exec(
417
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_canonical_guardian_requests_status ON canonical_guardian_requests(status)`,
418
+ );
419
+ raw.exec(
420
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_canonical_guardian_requests_guardian ON canonical_guardian_requests(guardian_external_user_id, status)`,
421
+ );
422
+ raw.exec(
423
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_canonical_guardian_requests_conversation ON canonical_guardian_requests(conversation_id, status)`,
424
+ );
425
+ raw.exec(
426
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_canonical_guardian_requests_source ON canonical_guardian_requests(source_type, status)`,
427
+ );
428
+ raw.exec(
429
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_canonical_guardian_requests_kind ON canonical_guardian_requests(kind, status)`,
430
+ );
431
+ raw.exec(
432
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_canonical_guardian_requests_request_code ON canonical_guardian_requests(request_code)`,
433
+ );
434
+
435
+ raw.exec("COMMIT");
436
+ } catch (e) {
437
+ try {
438
+ raw.exec("ROLLBACK");
439
+ } catch {
440
+ /* no active transaction */
441
+ }
442
+ throw e;
443
+ } finally {
444
+ raw.exec("PRAGMA foreign_keys = ON");
445
+ }
446
+ }
447
+
448
+ function rebuildCanonicalGuardianDeliveriesToText(raw: RawDb): void {
449
+ if (hasTextAffinity(raw, "canonical_guardian_deliveries", "created_at"))
450
+ return;
451
+
452
+ raw.exec("PRAGMA foreign_keys = OFF");
453
+ try {
454
+ raw.exec("BEGIN");
455
+
456
+ raw.exec(/*sql*/ `
457
+ CREATE TABLE canonical_guardian_deliveries_rb (
458
+ id TEXT PRIMARY KEY,
459
+ request_id TEXT NOT NULL REFERENCES canonical_guardian_requests(id) ON DELETE CASCADE,
460
+ destination_channel TEXT NOT NULL,
461
+ destination_conversation_id TEXT,
462
+ destination_chat_id TEXT,
463
+ destination_message_id TEXT,
464
+ status TEXT NOT NULL DEFAULT 'pending',
465
+ created_at TEXT NOT NULL,
466
+ updated_at TEXT NOT NULL
467
+ )
468
+ `);
469
+
470
+ raw.exec(/*sql*/ `
471
+ INSERT INTO canonical_guardian_deliveries_rb
472
+ SELECT id, request_id, destination_channel, destination_conversation_id,
473
+ destination_chat_id, destination_message_id, status,
474
+ created_at, updated_at
475
+ FROM canonical_guardian_deliveries
476
+ `);
477
+
478
+ raw.exec(/*sql*/ `DROP TABLE canonical_guardian_deliveries`);
479
+ raw.exec(
480
+ /*sql*/ `ALTER TABLE canonical_guardian_deliveries_rb RENAME TO canonical_guardian_deliveries`,
481
+ );
482
+
483
+ raw.exec(
484
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_canonical_guardian_deliveries_request_id ON canonical_guardian_deliveries(request_id)`,
485
+ );
486
+ raw.exec(
487
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_canonical_guardian_deliveries_status ON canonical_guardian_deliveries(status)`,
488
+ );
489
+ raw.exec(
490
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_canonical_guardian_deliveries_destination ON canonical_guardian_deliveries(destination_channel, destination_chat_id)`,
491
+ );
492
+
493
+ raw.exec("COMMIT");
494
+ } catch (e) {
495
+ try {
496
+ raw.exec("ROLLBACK");
497
+ } catch {
498
+ /* no active transaction */
499
+ }
500
+ throw e;
501
+ } finally {
502
+ raw.exec("PRAGMA foreign_keys = ON");
503
+ }
504
+ }
505
+
506
+ function rebuildScopedApprovalGrantsToText(raw: RawDb): void {
507
+ if (hasTextAffinity(raw, "scoped_approval_grants", "created_at")) return;
508
+
509
+ raw.exec("PRAGMA foreign_keys = OFF");
510
+ try {
511
+ raw.exec("BEGIN");
512
+
513
+ raw.exec(/*sql*/ `
514
+ CREATE TABLE scoped_approval_grants_rb (
515
+ id TEXT PRIMARY KEY,
516
+ scope_mode TEXT NOT NULL,
517
+ request_id TEXT,
518
+ tool_name TEXT,
519
+ input_digest TEXT,
520
+ request_channel TEXT NOT NULL,
521
+ decision_channel TEXT NOT NULL,
522
+ execution_channel TEXT,
523
+ conversation_id TEXT,
524
+ call_session_id TEXT,
525
+ requester_external_user_id TEXT,
526
+ guardian_external_user_id TEXT,
527
+ status TEXT NOT NULL,
528
+ expires_at TEXT NOT NULL,
529
+ consumed_at TEXT,
530
+ consumed_by_request_id TEXT,
531
+ created_at TEXT NOT NULL,
532
+ updated_at TEXT NOT NULL
533
+ )
534
+ `);
535
+
536
+ raw.exec(/*sql*/ `
537
+ INSERT INTO scoped_approval_grants_rb
538
+ SELECT id, scope_mode, request_id, tool_name, input_digest,
539
+ request_channel, decision_channel, execution_channel,
540
+ conversation_id, call_session_id,
541
+ requester_external_user_id, guardian_external_user_id,
542
+ status, expires_at, consumed_at, consumed_by_request_id,
543
+ created_at, updated_at
544
+ FROM scoped_approval_grants
545
+ `);
546
+
547
+ raw.exec(/*sql*/ `DROP TABLE scoped_approval_grants`);
548
+ raw.exec(
549
+ /*sql*/ `ALTER TABLE scoped_approval_grants_rb RENAME TO scoped_approval_grants`,
550
+ );
551
+
552
+ raw.exec(
553
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_scoped_grants_request_id ON scoped_approval_grants(request_id) WHERE request_id IS NOT NULL`,
554
+ );
555
+ raw.exec(
556
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_scoped_grants_tool_sig ON scoped_approval_grants(tool_name, input_digest) WHERE tool_name IS NOT NULL`,
557
+ );
558
+ raw.exec(
559
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_scoped_grants_status_expires ON scoped_approval_grants(status, expires_at)`,
560
+ );
561
+
562
+ raw.exec("COMMIT");
563
+ } catch (e) {
564
+ try {
565
+ raw.exec("ROLLBACK");
566
+ } catch {
567
+ /* no active transaction */
568
+ }
569
+ throw e;
570
+ } finally {
571
+ raw.exec("PRAGMA foreign_keys = ON");
572
+ }
573
+ }
@@ -1,4 +1,5 @@
1
- import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
2
3
  import { withCrashRecovery } from "./validate-migration-state.js";
3
4
 
4
5
  /**
@@ -62,3 +63,52 @@ export function migrateRenameGmailProviderKeyToGoogle(
62
63
  },
63
64
  );
64
65
  }
66
+
67
+ /**
68
+ * Reverse: rename "integration:google" back to "integration:gmail" across
69
+ * OAuth tables.
70
+ *
71
+ * Mirrors the forward migration logic but in the opposite direction. If
72
+ * `integration:gmail` already exists (shouldn't normally happen on rollback),
73
+ * deletes the google rows to avoid duplicates.
74
+ */
75
+ export function migrateRenameGmailProviderKeyToGoogleDown(
76
+ database: DrizzleDb,
77
+ ): void {
78
+ const raw = getSqliteFrom(database);
79
+
80
+ raw.exec("PRAGMA foreign_keys = OFF");
81
+ try {
82
+ const gmailExists = raw
83
+ .prepare(
84
+ /*sql*/ `SELECT 1 FROM oauth_providers WHERE provider_key = 'integration:gmail'`,
85
+ )
86
+ .get();
87
+
88
+ if (gmailExists) {
89
+ // Old gmail rows already exist — delete the google ones to avoid duplication.
90
+ raw.exec(
91
+ /*sql*/ `DELETE FROM oauth_connections WHERE provider_key = 'integration:google'`,
92
+ );
93
+ raw.exec(
94
+ /*sql*/ `DELETE FROM oauth_apps WHERE provider_key = 'integration:google'`,
95
+ );
96
+ raw.exec(
97
+ /*sql*/ `DELETE FROM oauth_providers WHERE provider_key = 'integration:google'`,
98
+ );
99
+ } else {
100
+ // Rename google back to gmail — children first, then parent.
101
+ raw.exec(
102
+ /*sql*/ `UPDATE oauth_connections SET provider_key = 'integration:gmail' WHERE provider_key = 'integration:google'`,
103
+ );
104
+ raw.exec(
105
+ /*sql*/ `UPDATE oauth_apps SET provider_key = 'integration:gmail' WHERE provider_key = 'integration:google'`,
106
+ );
107
+ raw.exec(
108
+ /*sql*/ `UPDATE oauth_providers SET provider_key = 'integration:gmail' WHERE provider_key = 'integration:google'`,
109
+ );
110
+ }
111
+ } finally {
112
+ raw.exec("PRAGMA foreign_keys = ON");
113
+ }
114
+ }
@@ -1,4 +1,5 @@
1
- import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
2
3
  import { withCrashRecovery } from "./validate-migration-state.js";
3
4
 
4
5
  /**
@@ -51,3 +52,48 @@ export function migrateRenameThreadStartersTable(database: DrizzleDb): void {
51
52
  },
52
53
  );
53
54
  }
55
+
56
+ /**
57
+ * Reverse: rename conversation_starters back to thread_starters and recreate
58
+ * old index names.
59
+ *
60
+ * Idempotent — skips if the old table already exists or the new table is
61
+ * absent.
62
+ */
63
+ export function migrateRenameThreadStartersTableDown(
64
+ database: DrizzleDb,
65
+ ): void {
66
+ const raw = getSqliteFrom(database);
67
+
68
+ // Guard: new table must exist
69
+ const newTableExists = raw
70
+ .query(
71
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'conversation_starters'`,
72
+ )
73
+ .get();
74
+ if (!newTableExists) return;
75
+
76
+ // Guard: old table must not already exist
77
+ const oldTableExists = raw
78
+ .query(
79
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'thread_starters'`,
80
+ )
81
+ .get();
82
+ if (oldTableExists) return;
83
+
84
+ // Rename the table back
85
+ raw.exec(
86
+ /*sql*/ `ALTER TABLE conversation_starters RENAME TO thread_starters`,
87
+ );
88
+
89
+ // Drop new indexes and recreate with old names
90
+ raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_conversation_starters_batch`);
91
+ raw.exec(
92
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_thread_starters_batch ON thread_starters(generation_batch, created_at)`,
93
+ );
94
+
95
+ raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_conversation_starters_card_type`);
96
+ raw.exec(
97
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_thread_starters_card_type ON thread_starters(card_type, scope_id)`,
98
+ );
99
+ }
@@ -34,3 +34,16 @@ export function migrateDropCapabilityCardState(database: DrizzleDb): void {
34
34
  raw.exec(/*sql*/ `DROP TABLE IF EXISTS capability_card_categories`);
35
35
  });
36
36
  }
37
+
38
+ /**
39
+ * Reverse: no-op.
40
+ *
41
+ * The forward migration deleted rows (card-type conversation starters,
42
+ * generate_capability_cards jobs, capability_cards checkpoints) and dropped
43
+ * the capability_card_categories table. The deleted data cannot be restored
44
+ * — it was discarded as dead state after the capability card feature was
45
+ * removed.
46
+ */
47
+ export function migrateDropCapabilityCardStateDown(_database: DrizzleDb): void {
48
+ // No-op — see comment above.
49
+ }
@@ -64,3 +64,19 @@ export function migrateBackfillInlineAttachmentsToDisk(
64
64
  },
65
65
  );
66
66
  }
67
+
68
+ /**
69
+ * Reverse: no-op.
70
+ *
71
+ * The forward migration moved attachment data from inline base64 in the
72
+ * database to on-disk files and cleared the dataBase64 column. The original
73
+ * base64 data has been deleted from the DB, and re-reading it from disk
74
+ * back into the database would be unreliable (file paths may have changed,
75
+ * disk files may have been cleaned up). The on-disk files remain intact
76
+ * and functional.
77
+ */
78
+ export function migrateBackfillInlineAttachmentsToDiskDown(
79
+ _database: DrizzleDb,
80
+ ): void {
81
+ // No-op — see comment above.
82
+ }
@@ -1,4 +1,5 @@
1
- import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
2
3
  import { withCrashRecovery } from "./validate-migration-state.js";
3
4
 
4
5
  /**
@@ -44,3 +45,29 @@ export function migrateRenameThreadStartersCheckpoints(
44
45
  },
45
46
  );
46
47
  }
48
+
49
+ /**
50
+ * Reverse: rename checkpoint keys from "conversation_starters:" back to
51
+ * "thread_starters:" prefix.
52
+ *
53
+ * Mirrors the forward migration but in reverse. Handles collisions the same
54
+ * way — if an old-prefix key already exists, the new-prefix key is dropped.
55
+ */
56
+ export function migrateRenameThreadStartersCheckpointsDown(
57
+ database: DrizzleDb,
58
+ ): void {
59
+ const raw = getSqliteFrom(database);
60
+
61
+ // 1. Delete conversation_starters: keys where a corresponding
62
+ // thread_starters: key already exists.
63
+ raw.exec(/*sql*/ `DELETE FROM memory_checkpoints
64
+ WHERE key LIKE 'conversation_starters:%'
65
+ AND replace(key, 'conversation_starters:', 'thread_starters:') IN (
66
+ SELECT key FROM memory_checkpoints WHERE key LIKE 'thread_starters:%'
67
+ )`);
68
+
69
+ // 2. Rename remaining keys back to the old prefix.
70
+ raw.exec(
71
+ /*sql*/ `UPDATE memory_checkpoints SET key = replace(key, 'conversation_starters:', 'thread_starters:') WHERE key LIKE 'conversation_starters:%'`,
72
+ );
73
+ }
@@ -0,0 +1,15 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+
3
+ /**
4
+ * Add skip_disclosure column to call_sessions so outbound calls can
5
+ * skip the disclosure announcement on a per-call basis.
6
+ */
7
+ export function migrateCallSessionSkipDisclosure(database: DrizzleDb): void {
8
+ try {
9
+ database.run(
10
+ /*sql*/ `ALTER TABLE call_sessions ADD COLUMN skip_disclosure INTEGER NOT NULL DEFAULT 0`,
11
+ );
12
+ } catch {
13
+ /* already exists */
14
+ }
15
+ }
@@ -0,0 +1,64 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+ import { withCrashRecovery } from "./validate-migration-state.js";
4
+
5
+ /**
6
+ * Backfill MIME types for audio attachments that were stored with
7
+ * "application/octet-stream" because the EXTENSION_MIME_MAP was
8
+ * missing audio format entries.
9
+ *
10
+ * Updates mime_type based on the file extension in original_filename.
11
+ */
12
+
13
+ const AUDIO_EXT_MIME: Record<string, string> = {
14
+ mp3: "audio/mpeg",
15
+ wav: "audio/wav",
16
+ ogg: "audio/ogg",
17
+ flac: "audio/flac",
18
+ aac: "audio/aac",
19
+ m4a: "audio/x-m4a",
20
+ opus: "audio/opus",
21
+ };
22
+
23
+ export function migrateBackfillAudioAttachmentMimeTypes(
24
+ database: DrizzleDb,
25
+ ): void {
26
+ withCrashRecovery(
27
+ database,
28
+ "migration_backfill_audio_attachment_mime_types_v1",
29
+ () => {
30
+ const raw = getSqliteFrom(database);
31
+
32
+ for (const [ext, mime] of Object.entries(AUDIO_EXT_MIME)) {
33
+ const pattern = `%.${ext}`;
34
+ const result = raw
35
+ .query(
36
+ `UPDATE attachments
37
+ SET mime_type = ?, kind = 'document'
38
+ WHERE lower(original_filename) LIKE ?
39
+ AND mime_type = 'application/octet-stream'`,
40
+ )
41
+ .run(mime, pattern);
42
+
43
+ if ((result as { changes?: number }).changes) {
44
+ console.log(
45
+ `Backfilled ${(result as { changes: number }).changes} .${ext} attachments → ${mime}`,
46
+ );
47
+ }
48
+ }
49
+ },
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Reverse: no-op.
55
+ *
56
+ * The forward migration corrected incorrect MIME types (application/octet-stream)
57
+ * to their proper audio/* values. Restoring the wrong MIME types would break
58
+ * audio playback and file handling. The corrected values are the desired state.
59
+ */
60
+ export function migrateBackfillAudioAttachmentMimeTypesDown(
61
+ _database: DrizzleDb,
62
+ ): void {
63
+ // No-op — see comment above.
64
+ }
@@ -0,0 +1,15 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+ import { withCrashRecovery } from "./validate-migration-state.js";
4
+
5
+ export function migrateContactsUserFileColumn(database: DrizzleDb): void {
6
+ withCrashRecovery(database, "migration_contacts_user_file_column_v1", () => {
7
+ const raw = getSqliteFrom(database);
8
+
9
+ try {
10
+ raw.exec(/*sql*/ `ALTER TABLE contacts ADD COLUMN user_file TEXT`);
11
+ } catch {
12
+ /* already exists */
13
+ }
14
+ });
15
+ }
@@ -128,6 +128,9 @@ export { migrateConversationForkLineage } from "./183-add-conversation-fork-line
128
128
  export { migrateLlmRequestLogProvider } from "./184-llm-request-log-provider.js";
129
129
  export { migrateScheduleQuietFlag } from "./188-schedule-quiet-flag.js";
130
130
  export { migrateDropSimplifiedMemory } from "./189-drop-simplified-memory.js";
131
+ export { migrateCallSessionSkipDisclosure } from "./190-call-session-skip-disclosure.js";
132
+ export { migrateBackfillAudioAttachmentMimeTypes } from "./191-backfill-audio-attachment-mime-types.js";
133
+ export { migrateContactsUserFileColumn } from "./192-contacts-user-file-column.js";
131
134
  export {
132
135
  MIGRATION_REGISTRY,
133
136
  type MigrationRegistryEntry,
@@ -135,6 +138,7 @@ export {
135
138
  } from "./registry.js";
136
139
  export {
137
140
  recoverCrashedMigrations,
141
+ rollbackMemoryMigration,
138
142
  validateMigrationState,
139
143
  withCrashRecovery,
140
144
  } from "./validate-migration-state.js";