@vellumai/assistant 0.5.1 → 0.5.2

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 (338) hide show
  1. package/ARCHITECTURE.md +54 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop.test.ts +111 -0
  6. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  7. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  8. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  9. package/src/__tests__/app-executors.test.ts +1 -291
  10. package/src/__tests__/app-git-history.test.ts +4 -4
  11. package/src/__tests__/app-routes-csp.test.ts +1 -0
  12. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  13. package/src/__tests__/attachments-store.test.ts +169 -21
  14. package/src/__tests__/attachments.test.ts +115 -1
  15. package/src/__tests__/btw-routes.test.ts +1 -0
  16. package/src/__tests__/canonical-guardian-store.test.ts +38 -0
  17. package/src/__tests__/channel-reply-delivery.test.ts +55 -0
  18. package/src/__tests__/checker.test.ts +54 -0
  19. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  20. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  21. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  22. package/src/__tests__/config-schema-cmd.test.ts +68 -21
  23. package/src/__tests__/config-schema.test.ts +1 -1
  24. package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
  25. package/src/__tests__/conversation-agent-loop.test.ts +290 -2
  26. package/src/__tests__/conversation-attachments.test.ts +17 -19
  27. package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
  28. package/src/__tests__/conversation-disk-view.test.ts +810 -0
  29. package/src/__tests__/conversation-error.test.ts +1 -1
  30. package/src/__tests__/conversation-fork-crud.test.ts +551 -0
  31. package/src/__tests__/conversation-fork-route.test.ts +386 -0
  32. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  33. package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
  34. package/src/__tests__/conversation-media-retry.test.ts +8 -2
  35. package/src/__tests__/conversation-queue.test.ts +36 -1
  36. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  37. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  38. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  39. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  40. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  41. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  42. package/src/__tests__/conversation-store.test.ts +24 -21
  43. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  44. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  45. package/src/__tests__/conversation-title-service.test.ts +137 -0
  46. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  47. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  48. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  49. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  50. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  51. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  52. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  53. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  54. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  55. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  56. package/src/__tests__/diagnostics-export.test.ts +70 -1
  57. package/src/__tests__/first-greeting.test.ts +80 -0
  58. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  59. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  60. package/src/__tests__/history-repair.test.ts +32 -10
  61. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  62. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  63. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  64. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  65. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  66. package/src/__tests__/media-generate-image.test.ts +47 -94
  67. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  68. package/src/__tests__/memory-recall-quality.test.ts +5 -5
  69. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  70. package/src/__tests__/migration-export-http.test.ts +3 -1
  71. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  72. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  73. package/src/__tests__/mime-builder.test.ts +3 -2
  74. package/src/__tests__/non-member-access-request.test.ts +12 -1
  75. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  76. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  77. package/src/__tests__/oauth-store.test.ts +115 -0
  78. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  79. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  80. package/src/__tests__/recording-handler.test.ts +17 -0
  81. package/src/__tests__/registry.test.ts +3 -8
  82. package/src/__tests__/relay-server.test.ts +1 -1
  83. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  84. package/src/__tests__/schema-transforms.test.ts +165 -5
  85. package/src/__tests__/server-history-render.test.ts +2 -2
  86. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  87. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  88. package/src/__tests__/starter-task-flow.test.ts +1 -0
  89. package/src/__tests__/suggestion-routes.test.ts +443 -0
  90. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  91. package/src/__tests__/swarm-recursion.test.ts +1 -0
  92. package/src/__tests__/swarm-tool.test.ts +1 -0
  93. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  94. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  95. package/src/__tests__/top-level-renderer.test.ts +22 -0
  96. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  97. package/src/__tests__/web-fetch.test.ts +6 -2
  98. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  99. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  100. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  101. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  102. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  103. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  104. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  105. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  106. package/src/agent/attachments.ts +27 -1
  107. package/src/agent/loop.ts +29 -1
  108. package/src/avatar/traits-png-sync.ts +80 -25
  109. package/src/bundler/app-bundler.ts +4 -4
  110. package/src/calls/call-domain.ts +1 -0
  111. package/src/calls/voice-session-bridge.ts +1 -0
  112. package/src/cli/commands/auth.ts +92 -0
  113. package/src/cli/commands/avatar.ts +7 -6
  114. package/src/cli/commands/config.ts +2 -0
  115. package/src/cli/commands/oauth/providers.ts +29 -0
  116. package/src/cli/program.ts +12 -0
  117. package/src/cli.ts +15 -48
  118. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  119. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  120. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  121. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  135. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  136. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  137. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  138. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  139. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  140. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  141. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  142. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  143. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  144. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  145. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  146. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  147. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  148. package/src/config/bundled-tool-registry.ts +2 -14
  149. package/src/config/feature-flag-registry.json +8 -0
  150. package/src/config/loader.ts +64 -0
  151. package/src/config/raw-config-utils.ts +30 -0
  152. package/src/config/schema-utils.ts +28 -7
  153. package/src/config/schema.ts +8 -0
  154. package/src/config/schemas/elevenlabs.ts +18 -0
  155. package/src/config/schemas/memory-lifecycle.ts +4 -2
  156. package/src/config/schemas/memory-storage.ts +1 -1
  157. package/src/config/schemas/services.ts +8 -6
  158. package/src/contacts/contact-store.ts +13 -6
  159. package/src/contacts/contacts-write.ts +0 -1
  160. package/src/context/window-manager.ts +13 -2
  161. package/src/daemon/conversation-agent-loop-handlers.ts +48 -7
  162. package/src/daemon/conversation-agent-loop.ts +56 -19
  163. package/src/daemon/conversation-attachments.ts +18 -36
  164. package/src/daemon/conversation-error.ts +2 -1
  165. package/src/daemon/conversation-history.ts +18 -4
  166. package/src/daemon/conversation-lifecycle.ts +39 -15
  167. package/src/daemon/conversation-messaging.ts +70 -26
  168. package/src/daemon/conversation-process.ts +58 -34
  169. package/src/daemon/conversation-runtime-assembly.ts +21 -38
  170. package/src/daemon/conversation-slash.ts +121 -256
  171. package/src/daemon/conversation-surfaces.ts +143 -20
  172. package/src/daemon/conversation-tool-setup.ts +0 -6
  173. package/src/daemon/conversation-workspace.ts +21 -1
  174. package/src/daemon/conversation.ts +51 -29
  175. package/src/daemon/first-greeting.ts +35 -0
  176. package/src/daemon/handlers/config-embeddings.ts +148 -0
  177. package/src/daemon/handlers/config-model.ts +71 -26
  178. package/src/daemon/handlers/conversations.ts +0 -23
  179. package/src/daemon/handlers/recording.ts +26 -21
  180. package/src/daemon/host-cu-proxy.ts +2 -2
  181. package/src/daemon/lifecycle.ts +106 -64
  182. package/src/daemon/message-protocol.ts +3 -0
  183. package/src/daemon/message-types/conversations.ts +19 -0
  184. package/src/daemon/message-types/messages.ts +1 -0
  185. package/src/daemon/message-types/shared.ts +2 -0
  186. package/src/daemon/message-types/surfaces.ts +2 -0
  187. package/src/daemon/message-types/upgrades.ts +23 -0
  188. package/src/daemon/server.ts +83 -12
  189. package/src/daemon/shutdown-handlers.ts +8 -5
  190. package/src/daemon/startup-error.ts +9 -0
  191. package/src/daemon/tool-side-effects.ts +11 -28
  192. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  193. package/src/instrument.ts +0 -4
  194. package/src/media/app-icon-generator.ts +2 -2
  195. package/src/memory/app-git-service.ts +28 -16
  196. package/src/memory/app-store.ts +230 -41
  197. package/src/memory/attachments-store.ts +558 -130
  198. package/src/memory/conversation-attention-store.ts +70 -0
  199. package/src/memory/conversation-crud.ts +442 -3
  200. package/src/memory/conversation-directories.ts +125 -0
  201. package/src/memory/conversation-disk-view.ts +390 -0
  202. package/src/memory/conversation-key-store.ts +17 -5
  203. package/src/memory/conversation-queries.ts +5 -1
  204. package/src/memory/conversation-title-service.ts +21 -49
  205. package/src/memory/db-init.ts +28 -0
  206. package/src/memory/embedding-backend.ts +42 -53
  207. package/src/memory/embedding-gemini.test.ts +4 -4
  208. package/src/memory/embedding-local.ts +1 -3
  209. package/src/memory/embedding-ollama.ts +1 -3
  210. package/src/memory/embedding-openai.ts +1 -3
  211. package/src/memory/indexer.ts +9 -7
  212. package/src/memory/items-extractor.ts +42 -13
  213. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  214. package/src/memory/job-handlers/embedding.test.ts +1 -4
  215. package/src/memory/llm-request-log-store.ts +100 -1
  216. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  217. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  218. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  219. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  220. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  221. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  222. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  223. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  224. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  225. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  226. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  227. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  228. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  229. package/src/memory/migrations/index.ts +7 -0
  230. package/src/memory/migrations/registry.ts +13 -0
  231. package/src/memory/retriever.test.ts +601 -2
  232. package/src/memory/retriever.ts +85 -9
  233. package/src/memory/schema/conversations.ts +6 -0
  234. package/src/memory/schema/infrastructure.ts +13 -7
  235. package/src/memory/schema/oauth.ts +6 -0
  236. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  237. package/src/notifications/copy-composer.ts +26 -0
  238. package/src/notifications/decision-engine.ts +14 -1
  239. package/src/notifications/emit-signal.ts +1 -1
  240. package/src/notifications/signal.ts +36 -0
  241. package/src/oauth/byo-connection.test.ts +1 -45
  242. package/src/oauth/byo-connection.ts +2 -8
  243. package/src/oauth/connect-orchestrator.ts +15 -11
  244. package/src/oauth/connection-resolver.test.ts +191 -0
  245. package/src/oauth/connection-resolver.ts +66 -38
  246. package/src/oauth/connection.ts +0 -1
  247. package/src/oauth/oauth-store.ts +97 -47
  248. package/src/oauth/platform-connection.test.ts +0 -1
  249. package/src/oauth/platform-connection.ts +11 -3
  250. package/src/oauth/seed-providers.ts +78 -3
  251. package/src/oauth/token-persistence.ts +16 -10
  252. package/src/permissions/checker.ts +71 -8
  253. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  254. package/src/providers/anthropic/client.ts +8 -1
  255. package/src/providers/failover.ts +4 -1
  256. package/src/providers/gemini/client.ts +50 -0
  257. package/src/providers/model-catalog.ts +92 -0
  258. package/src/providers/model-intents.ts +29 -20
  259. package/src/providers/openai/client.ts +49 -0
  260. package/src/providers/types.ts +2 -0
  261. package/src/runtime/access-request-helper.ts +16 -7
  262. package/src/runtime/auth/credential-service.ts +3 -1
  263. package/src/runtime/auth/route-policy.ts +14 -1
  264. package/src/runtime/btw-sidechain.ts +101 -0
  265. package/src/runtime/channel-reply-delivery.ts +17 -1
  266. package/src/runtime/http-router.ts +3 -1
  267. package/src/runtime/http-server.ts +196 -141
  268. package/src/runtime/http-types.ts +1 -0
  269. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  270. package/src/runtime/routes/access-request-decision.ts +41 -0
  271. package/src/runtime/routes/app-management-routes.ts +6 -3
  272. package/src/runtime/routes/app-routes.ts +7 -3
  273. package/src/runtime/routes/approval-routes.ts +1 -0
  274. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  275. package/src/runtime/routes/attachment-routes.ts +45 -15
  276. package/src/runtime/routes/btw-routes.ts +21 -61
  277. package/src/runtime/routes/conversation-management-routes.ts +68 -0
  278. package/src/runtime/routes/conversation-query-routes.ts +180 -10
  279. package/src/runtime/routes/conversation-routes.ts +222 -28
  280. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  281. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  282. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  283. package/src/runtime/routes/llm-context-normalization.ts +1199 -0
  284. package/src/runtime/routes/log-export-routes.ts +3 -0
  285. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  286. package/src/runtime/routes/memory-item-routes.ts +4 -0
  287. package/src/runtime/routes/migration-routes.ts +4 -1
  288. package/src/runtime/routes/oauth-apps.ts +291 -0
  289. package/src/runtime/routes/secret-routes.ts +28 -1
  290. package/src/runtime/routes/settings-routes.ts +14 -0
  291. package/src/runtime/routes/trace-event-routes.ts +4 -1
  292. package/src/schedule/schedule-store.ts +9 -21
  293. package/src/security/secure-keys.ts +21 -0
  294. package/src/signals/bash.ts +1 -1
  295. package/src/swarm/backend-claude-code.ts +3 -6
  296. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  297. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  298. package/src/tools/AGENTS.md +6 -10
  299. package/src/tools/apps/executors.ts +17 -232
  300. package/src/tools/claude-code/claude-code.ts +2 -3
  301. package/src/tools/credentials/vault.ts +7 -12
  302. package/src/tools/host-filesystem/read.ts +13 -10
  303. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  304. package/src/tools/schedule/list.ts +2 -7
  305. package/src/tools/schema-transforms.ts +5 -0
  306. package/src/tools/shared/filesystem/format-diff.ts +2 -7
  307. package/src/tools/skills/execute.ts +1 -1
  308. package/src/tools/tool-manifest.ts +0 -6
  309. package/src/tools/ui-surface/definitions.ts +2 -2
  310. package/src/util/device-id.ts +28 -5
  311. package/src/util/platform.ts +6 -0
  312. package/src/util/pricing.ts +1 -0
  313. package/src/util/retry.ts +1 -3
  314. package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
  315. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  316. package/src/workspace/migrations/006-services-config.ts +5 -0
  317. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  318. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  319. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  320. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  321. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  322. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  323. package/src/workspace/migrations/registry.ts +10 -0
  324. package/src/workspace/top-level-renderer.ts +12 -0
  325. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  326. package/src/__tests__/asset-search-tool.test.ts +0 -536
  327. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  328. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  329. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  330. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  331. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  332. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  333. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  334. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  335. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  336. package/src/daemon/media-visibility-policy.ts +0 -59
  337. package/src/tools/assets/materialize.ts +0 -248
  338. package/src/tools/assets/search.ts +0 -400
@@ -0,0 +1,241 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { Database } from "bun:sqlite";
5
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
6
+
7
+ import { drizzle } from "drizzle-orm/bun-sqlite";
8
+
9
+ const testDir = mkdtempSync(join(tmpdir(), "conversation-fork-lineage-"));
10
+ const dbPath = join(testDir, "test.db");
11
+ const originalBunTest = process.env.BUN_TEST;
12
+
13
+ mock.module("../util/platform.js", () => ({
14
+ getDataDir: () => testDir,
15
+ isMacOS: () => process.platform === "darwin",
16
+ isLinux: () => process.platform === "linux",
17
+ isWindows: () => process.platform === "win32",
18
+ getPidPath: () => join(testDir, "test.pid"),
19
+ getDbPath: () => dbPath,
20
+ getLogPath: () => join(testDir, "test.log"),
21
+ ensureDataDir: () => {},
22
+ }));
23
+
24
+ mock.module("../util/logger.js", () => ({
25
+ getLogger: () =>
26
+ new Proxy({} as Record<string, unknown>, {
27
+ get: () => () => {},
28
+ }),
29
+ }));
30
+
31
+ import { initializeDb, resetDb } from "../memory/db.js";
32
+ import { getSqliteFrom } from "../memory/db-connection.js";
33
+ import { migrateConversationForkLineage } from "../memory/migrations/183-add-conversation-fork-lineage.js";
34
+ import * as schema from "../memory/schema.js";
35
+
36
+ function createTestDb() {
37
+ const sqlite = new Database(":memory:");
38
+ sqlite.exec("PRAGMA journal_mode=WAL");
39
+ sqlite.exec("PRAGMA foreign_keys = ON");
40
+ return drizzle(sqlite, { schema });
41
+ }
42
+
43
+ function getColumnNames(raw: Database): string[] {
44
+ return (
45
+ raw.query(`PRAGMA table_info(conversations)`).all() as Array<{ name: string }>
46
+ ).map((column) => column.name);
47
+ }
48
+
49
+ function hasIndex(raw: Database, indexName: string): boolean {
50
+ const row = raw
51
+ .query(`SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = ?`)
52
+ .get(indexName);
53
+ return row != null;
54
+ }
55
+
56
+ function bootstrapPreLineageConversations(raw: Database): void {
57
+ raw.exec(/*sql*/ `
58
+ CREATE TABLE conversations (
59
+ id TEXT PRIMARY KEY,
60
+ title TEXT,
61
+ created_at INTEGER NOT NULL,
62
+ updated_at INTEGER NOT NULL,
63
+ total_input_tokens INTEGER NOT NULL DEFAULT 0,
64
+ total_output_tokens INTEGER NOT NULL DEFAULT 0,
65
+ total_estimated_cost REAL NOT NULL DEFAULT 0,
66
+ context_summary TEXT,
67
+ context_compacted_message_count INTEGER NOT NULL DEFAULT 0,
68
+ context_compacted_at INTEGER,
69
+ conversation_type TEXT NOT NULL DEFAULT 'standard',
70
+ source TEXT NOT NULL DEFAULT 'user',
71
+ memory_scope_id TEXT NOT NULL DEFAULT 'default',
72
+ origin_channel TEXT,
73
+ origin_interface TEXT,
74
+ is_auto_title INTEGER NOT NULL DEFAULT 1,
75
+ schedule_job_id TEXT
76
+ )
77
+ `);
78
+ }
79
+
80
+ function removeTestDbFiles(): void {
81
+ resetDb();
82
+ rmSync(dbPath, { force: true });
83
+ rmSync(`${dbPath}-shm`, { force: true });
84
+ rmSync(`${dbPath}-wal`, { force: true });
85
+ }
86
+
87
+ describe("conversation fork lineage migration", () => {
88
+ beforeEach(() => {
89
+ process.env.BUN_TEST = "0";
90
+ removeTestDbFiles();
91
+ });
92
+
93
+ afterAll(() => {
94
+ process.env.BUN_TEST = originalBunTest;
95
+ removeTestDbFiles();
96
+ try {
97
+ rmSync(testDir, { recursive: true });
98
+ } catch {
99
+ /* best effort */
100
+ }
101
+ });
102
+
103
+ test("fresh DB initialization includes nullable lineage columns and parent lookup index", () => {
104
+ initializeDb();
105
+
106
+ const raw = new Database(dbPath);
107
+ const columns = getColumnNames(raw);
108
+
109
+ expect(columns).toContain("fork_parent_conversation_id");
110
+ expect(columns).toContain("fork_parent_message_id");
111
+ expect(hasIndex(raw, "idx_conversations_fork_parent_conversation_id")).toBe(
112
+ true,
113
+ );
114
+
115
+ const forkColumns = (
116
+ raw.query(`PRAGMA table_info(conversations)`).all() as Array<{
117
+ name: string;
118
+ notnull: number;
119
+ }>
120
+ ).filter(
121
+ (column) =>
122
+ column.name === "fork_parent_conversation_id" ||
123
+ column.name === "fork_parent_message_id",
124
+ );
125
+
126
+ expect(forkColumns).toHaveLength(2);
127
+ expect(forkColumns.every((column) => column.notnull === 0)).toBe(true);
128
+ raw.close();
129
+ });
130
+
131
+ test("migration upgrades the previous schema without disturbing existing conversation rows", () => {
132
+ const db = createTestDb();
133
+ const raw = getSqliteFrom(db);
134
+ const now = Date.now();
135
+
136
+ bootstrapPreLineageConversations(raw);
137
+ raw.exec(/*sql*/ `
138
+ INSERT INTO conversations (
139
+ id,
140
+ title,
141
+ created_at,
142
+ updated_at,
143
+ conversation_type,
144
+ source,
145
+ memory_scope_id,
146
+ is_auto_title
147
+ ) VALUES (
148
+ 'conv-upgrade',
149
+ 'Existing conversation',
150
+ ${now},
151
+ ${now},
152
+ 'standard',
153
+ 'user',
154
+ 'default',
155
+ 1
156
+ )
157
+ `);
158
+
159
+ migrateConversationForkLineage(db);
160
+
161
+ expect(getColumnNames(raw)).toContain("fork_parent_conversation_id");
162
+ expect(getColumnNames(raw)).toContain("fork_parent_message_id");
163
+ expect(hasIndex(raw, "idx_conversations_fork_parent_conversation_id")).toBe(
164
+ true,
165
+ );
166
+
167
+ const row = raw
168
+ .query(
169
+ `SELECT id, title, fork_parent_conversation_id, fork_parent_message_id FROM conversations WHERE id = 'conv-upgrade'`,
170
+ )
171
+ .get() as {
172
+ id: string;
173
+ title: string | null;
174
+ fork_parent_conversation_id: string | null;
175
+ fork_parent_message_id: string | null;
176
+ } | null;
177
+
178
+ expect(row).toEqual({
179
+ id: "conv-upgrade",
180
+ title: "Existing conversation",
181
+ fork_parent_conversation_id: null,
182
+ fork_parent_message_id: null,
183
+ });
184
+ });
185
+
186
+ test("re-running the migration preserves existing lineage data", () => {
187
+ const db = createTestDb();
188
+ const raw = getSqliteFrom(db);
189
+ const now = Date.now();
190
+
191
+ bootstrapPreLineageConversations(raw);
192
+ raw.exec(/*sql*/ `
193
+ INSERT INTO conversations (
194
+ id,
195
+ title,
196
+ created_at,
197
+ updated_at,
198
+ conversation_type,
199
+ source,
200
+ memory_scope_id,
201
+ is_auto_title
202
+ ) VALUES (
203
+ 'conv-rerun',
204
+ 'Forked conversation',
205
+ ${now},
206
+ ${now},
207
+ 'standard',
208
+ 'user',
209
+ 'default',
210
+ 1
211
+ )
212
+ `);
213
+
214
+ migrateConversationForkLineage(db);
215
+ raw.exec(/*sql*/ `
216
+ UPDATE conversations
217
+ SET fork_parent_conversation_id = 'conv-parent',
218
+ fork_parent_message_id = 'msg-parent'
219
+ WHERE id = 'conv-rerun'
220
+ `);
221
+
222
+ expect(() => migrateConversationForkLineage(db)).not.toThrow();
223
+
224
+ const row = raw
225
+ .query(
226
+ `SELECT fork_parent_conversation_id, fork_parent_message_id FROM conversations WHERE id = 'conv-rerun'`,
227
+ )
228
+ .get() as {
229
+ fork_parent_conversation_id: string | null;
230
+ fork_parent_message_id: string | null;
231
+ } | null;
232
+
233
+ expect(row).toEqual({
234
+ fork_parent_conversation_id: "conv-parent",
235
+ fork_parent_message_id: "msg-parent",
236
+ });
237
+ expect(hasIndex(raw, "idx_conversations_fork_parent_conversation_id")).toBe(
238
+ true,
239
+ );
240
+ });
241
+ });
@@ -0,0 +1,214 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { Database } from "bun:sqlite";
5
+ import { afterAll, afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
6
+
7
+ import { drizzle } from "drizzle-orm/bun-sqlite";
8
+
9
+ const testDir = mkdtempSync(join(tmpdir(), "llm-request-log-provider-"));
10
+ const dbPath = join(testDir, "test.db");
11
+ const originalBunTest = process.env.BUN_TEST;
12
+
13
+ mock.module("../util/platform.js", () => ({
14
+ getDataDir: () => testDir,
15
+ isMacOS: () => process.platform === "darwin",
16
+ isLinux: () => process.platform === "linux",
17
+ isWindows: () => process.platform === "win32",
18
+ getPidPath: () => join(testDir, "test.pid"),
19
+ getDbPath: () => dbPath,
20
+ getLogPath: () => join(testDir, "test.log"),
21
+ ensureDataDir: () => {},
22
+ }));
23
+
24
+ mock.module("../util/logger.js", () => ({
25
+ getLogger: () =>
26
+ new Proxy({} as Record<string, unknown>, {
27
+ get: () => () => {},
28
+ }),
29
+ }));
30
+
31
+ import { initializeDb, resetDb } from "../memory/db.js";
32
+ import { getSqliteFrom } from "../memory/db-connection.js";
33
+ import { migrateLlmRequestLogProvider } from "../memory/migrations/184-llm-request-log-provider.js";
34
+ import * as schema from "../memory/schema.js";
35
+
36
+ function createTestDb() {
37
+ const sqlite = new Database(":memory:");
38
+ sqlite.exec("PRAGMA journal_mode=WAL");
39
+ sqlite.exec("PRAGMA foreign_keys = ON");
40
+ return drizzle(sqlite, { schema });
41
+ }
42
+
43
+ function getColumnInfo(raw: Database): Array<{ name: string; notnull: number }> {
44
+ return raw.query(`PRAGMA table_info(llm_request_logs)`).all() as Array<{
45
+ name: string;
46
+ notnull: number;
47
+ }>;
48
+ }
49
+
50
+ function bootstrapPreProviderLlmRequestLogs(raw: Database): void {
51
+ raw.exec(/*sql*/ `
52
+ CREATE TABLE llm_request_logs (
53
+ id TEXT PRIMARY KEY,
54
+ conversation_id TEXT NOT NULL,
55
+ message_id TEXT,
56
+ request_payload TEXT NOT NULL,
57
+ response_payload TEXT NOT NULL,
58
+ created_at INTEGER NOT NULL
59
+ )
60
+ `);
61
+ }
62
+
63
+ function removeTestDbFiles(): void {
64
+ rmSync(dbPath, { force: true });
65
+ rmSync(`${dbPath}-shm`, { force: true });
66
+ rmSync(`${dbPath}-wal`, { force: true });
67
+ }
68
+
69
+ describe("llm_request_logs provider migration", () => {
70
+ beforeEach(() => {
71
+ process.env.BUN_TEST = "0";
72
+ resetDb();
73
+ removeTestDbFiles();
74
+ });
75
+
76
+ afterEach(() => {
77
+ resetDb();
78
+ removeTestDbFiles();
79
+ });
80
+
81
+ afterAll(() => {
82
+ if (originalBunTest === undefined) {
83
+ delete process.env.BUN_TEST;
84
+ } else {
85
+ process.env.BUN_TEST = originalBunTest;
86
+ }
87
+ resetDb();
88
+ removeTestDbFiles();
89
+ try {
90
+ rmSync(testDir, { recursive: true });
91
+ } catch {
92
+ /* best effort */
93
+ }
94
+ });
95
+
96
+ test("fresh DB initialization includes llm_request_logs.provider", () => {
97
+ initializeDb();
98
+
99
+ const raw = new Database(dbPath);
100
+ const columns = getColumnInfo(raw);
101
+
102
+ expect(columns.some((column) => column.name === "provider")).toBe(true);
103
+ expect(columns.find((column) => column.name === "provider")?.notnull).toBe(
104
+ 0,
105
+ );
106
+
107
+ raw.close();
108
+ });
109
+
110
+ test("migration upgrades the pre-provider schema without disturbing rows", () => {
111
+ const db = createTestDb();
112
+ const raw = getSqliteFrom(db);
113
+
114
+ bootstrapPreProviderLlmRequestLogs(raw);
115
+ raw.exec(/*sql*/ `
116
+ INSERT INTO llm_request_logs (
117
+ id,
118
+ conversation_id,
119
+ message_id,
120
+ request_payload,
121
+ response_payload,
122
+ created_at
123
+ ) VALUES (
124
+ 'log-upgrade',
125
+ 'conv-1',
126
+ 'msg-1',
127
+ '{}',
128
+ '{"ok":true}',
129
+ 1000
130
+ )
131
+ `);
132
+
133
+ migrateLlmRequestLogProvider(db);
134
+
135
+ expect(getColumnInfo(raw).some((column) => column.name === "provider")).toBe(
136
+ true,
137
+ );
138
+
139
+ const row = raw
140
+ .query(
141
+ `SELECT id, conversation_id, message_id, provider, request_payload, response_payload, created_at
142
+ FROM llm_request_logs
143
+ WHERE id = 'log-upgrade'`,
144
+ )
145
+ .get() as
146
+ | {
147
+ id: string;
148
+ conversation_id: string;
149
+ message_id: string | null;
150
+ provider: string | null;
151
+ request_payload: string;
152
+ response_payload: string;
153
+ created_at: number;
154
+ }
155
+ | null;
156
+
157
+ expect(row).toEqual({
158
+ id: "log-upgrade",
159
+ conversation_id: "conv-1",
160
+ message_id: "msg-1",
161
+ provider: null,
162
+ request_payload: "{}",
163
+ response_payload: '{"ok":true}',
164
+ created_at: 1000,
165
+ });
166
+
167
+ raw.close();
168
+ });
169
+
170
+ test("re-running the migration preserves populated provider values", () => {
171
+ const db = createTestDb();
172
+ const raw = getSqliteFrom(db);
173
+
174
+ bootstrapPreProviderLlmRequestLogs(raw);
175
+ raw.exec(/*sql*/ `
176
+ INSERT INTO llm_request_logs (
177
+ id,
178
+ conversation_id,
179
+ message_id,
180
+ request_payload,
181
+ response_payload,
182
+ created_at
183
+ ) VALUES (
184
+ 'log-rerun',
185
+ 'conv-2',
186
+ 'msg-2',
187
+ '{}',
188
+ '{"ok":true}',
189
+ 2000
190
+ )
191
+ `);
192
+
193
+ migrateLlmRequestLogProvider(db);
194
+ raw.exec(/*sql*/ `
195
+ UPDATE llm_request_logs
196
+ SET provider = 'anthropic'
197
+ WHERE id = 'log-rerun'
198
+ `);
199
+
200
+ expect(() => migrateLlmRequestLogProvider(db)).not.toThrow();
201
+
202
+ const row = raw
203
+ .query(
204
+ `SELECT provider FROM llm_request_logs WHERE id = 'log-rerun'`,
205
+ )
206
+ .get() as { provider: string | null } | null;
207
+
208
+ expect(row).toEqual({
209
+ provider: "anthropic",
210
+ });
211
+
212
+ raw.close();
213
+ });
214
+ });
@@ -5,11 +5,13 @@
5
5
  * (specific ID → most recent assistant → any message → empty conversation).
6
6
  */
7
7
 
8
- import { mkdtempSync, rmSync } from "node:fs";
8
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
9
9
  import { tmpdir } from "node:os";
10
10
  import { join } from "node:path";
11
11
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
12
12
 
13
+ import JSZip from "jszip";
14
+
13
15
  const testDir = mkdtempSync(join(tmpdir(), "diagnostics-export-test-"));
14
16
 
15
17
  mock.module("../util/platform.js", () => ({
@@ -90,6 +92,27 @@ function seedMessage(
90
92
  );
91
93
  }
92
94
 
95
+ function seedLlmRequestLog(
96
+ id: string,
97
+ conversationId: string,
98
+ provider: string | null,
99
+ requestPayload: unknown,
100
+ responsePayload: unknown,
101
+ createdAt: number,
102
+ ): void {
103
+ db().run(
104
+ "INSERT INTO llm_request_logs (id, conversation_id, provider, request_payload, response_payload, created_at) VALUES (?, ?, ?, ?, ?, ?)",
105
+ [
106
+ id,
107
+ conversationId,
108
+ provider,
109
+ JSON.stringify(requestPayload),
110
+ JSON.stringify(responsePayload),
111
+ createdAt,
112
+ ],
113
+ );
114
+ }
115
+
93
116
  function seedConversationKey(
94
117
  conversationKey: string,
95
118
  conversationId: string,
@@ -102,6 +125,7 @@ function seedConversationKey(
102
125
 
103
126
  function cleanDb(): void {
104
127
  db().run("DELETE FROM messages");
128
+ db().run("DELETE FROM llm_request_logs");
105
129
  db().run("DELETE FROM conversation_keys");
106
130
  db().run("DELETE FROM conversations");
107
131
  }
@@ -216,4 +240,49 @@ describe("diagnostics export", () => {
216
240
  const json = (await res.json()) as { success: boolean };
217
241
  expect(json.success).toBe(true);
218
242
  });
243
+
244
+ test("preserves llm request provider identity in the exported JSONL", async () => {
245
+ const convId = "conv-7";
246
+ const now = Date.now();
247
+
248
+ seedConversation(convId);
249
+ seedMessage("msg-user-7", convId, "user", "hello", now - 1000);
250
+ seedMessage("msg-assistant-7", convId, "assistant", "world", now);
251
+ seedLlmRequestLog(
252
+ "log-7",
253
+ convId,
254
+ "openrouter",
255
+ { model: "openai/gpt-4.1-mini", input: "hello" },
256
+ { output: "world" },
257
+ now,
258
+ );
259
+
260
+ const res = await callExport({ conversationId: convId });
261
+ expect(res.status).toBe(200);
262
+ const json = (await res.json()) as { success: boolean; filePath: string };
263
+ expect(json.success).toBe(true);
264
+
265
+ const zip = await JSZip.loadAsync(readFileSync(json.filePath));
266
+ const llmRequests = zip.file("llm_requests.jsonl");
267
+ expect(llmRequests).not.toBeNull();
268
+
269
+ const lines = (await llmRequests!.async("string")).trim().split("\n");
270
+ expect(lines).toHaveLength(1);
271
+
272
+ const row = JSON.parse(lines[0]) as {
273
+ id: string;
274
+ conversationId: string;
275
+ provider?: string | null;
276
+ request: unknown;
277
+ response: unknown;
278
+ };
279
+
280
+ expect(row).toMatchObject({
281
+ id: "log-7",
282
+ conversationId: convId,
283
+ provider: "openrouter",
284
+ });
285
+
286
+ rmSync(json.filePath, { force: true });
287
+ });
219
288
  });
@@ -0,0 +1,80 @@
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
5
+
6
+ let tempDir: string;
7
+
8
+ mock.module("../util/platform.js", () => ({
9
+ getWorkspacePromptPath: mock((file: string) => join(tempDir, file)),
10
+ getWorkspaceDir: () => tempDir,
11
+ getRootDir: () => tempDir,
12
+ getDataDir: () => join(tempDir, "data"),
13
+ getPlatformName: () => "darwin",
14
+ isMacOS: () => false,
15
+ isLinux: () => false,
16
+ isWindows: () => false,
17
+ ensureDataDir: () => {},
18
+ getDbPath: () => "",
19
+ getLogPath: () => "",
20
+ getHistoryPath: () => "",
21
+ getHooksDir: () => "",
22
+ getSessionTokenPath: () => "",
23
+ getPlatformTokenPath: () => "",
24
+ getPidPath: () => "",
25
+ }));
26
+
27
+ const { isWakeUpGreeting, getCannedFirstGreeting, CANNED_FIRST_GREETING } =
28
+ await import("../daemon/first-greeting.js");
29
+
30
+ describe("first-greeting", () => {
31
+ beforeEach(() => {
32
+ tempDir = join(tmpdir(), `first-greeting-test-${Date.now()}`);
33
+ mkdirSync(tempDir, { recursive: true });
34
+ });
35
+
36
+ afterEach(() => {
37
+ rmSync(tempDir, { recursive: true, force: true });
38
+ });
39
+
40
+ describe("isWakeUpGreeting", () => {
41
+ it("returns true for wake-up greeting with 0 messages and BOOTSTRAP.md present", () => {
42
+ writeFileSync(join(tempDir, "BOOTSTRAP.md"), "bootstrap content");
43
+ expect(isWakeUpGreeting("Wake up, my friend.", 0)).toBe(true);
44
+ });
45
+
46
+ it("returns true for case variations", () => {
47
+ writeFileSync(join(tempDir, "BOOTSTRAP.md"), "bootstrap content");
48
+ expect(isWakeUpGreeting("wake up, my friend.", 0)).toBe(true);
49
+ expect(isWakeUpGreeting("WAKE UP, MY FRIEND.", 0)).toBe(true);
50
+ expect(isWakeUpGreeting("Wake Up, My Friend.", 0)).toBe(true);
51
+ });
52
+
53
+ it("returns false when content doesn't match wake-up greeting", () => {
54
+ writeFileSync(join(tempDir, "BOOTSTRAP.md"), "bootstrap content");
55
+ expect(isWakeUpGreeting("Hello", 0)).toBe(false);
56
+ expect(isWakeUpGreeting("Hey there", 0)).toBe(false);
57
+ expect(isWakeUpGreeting("Wake up", 0)).toBe(false);
58
+ });
59
+
60
+ it("returns false when conversationMessageCount > 0", () => {
61
+ writeFileSync(join(tempDir, "BOOTSTRAP.md"), "bootstrap content");
62
+ expect(isWakeUpGreeting("Wake up, my friend.", 1)).toBe(false);
63
+ expect(isWakeUpGreeting("Wake up, my friend.", 5)).toBe(false);
64
+ });
65
+
66
+ it("returns false when BOOTSTRAP.md doesn't exist", () => {
67
+ expect(existsSync(join(tempDir, "BOOTSTRAP.md"))).toBe(false);
68
+ expect(isWakeUpGreeting("Wake up, my friend.", 0)).toBe(false);
69
+ });
70
+ });
71
+
72
+ describe("getCannedFirstGreeting", () => {
73
+ it("returns the expected greeting string", () => {
74
+ const greeting = getCannedFirstGreeting();
75
+ expect(greeting).toBe(CANNED_FIRST_GREETING);
76
+ expect(greeting).toContain("brand new");
77
+ expect(greeting).toContain("no name, no memories");
78
+ });
79
+ });
80
+ });
@@ -24,6 +24,7 @@ const ALLOWLIST = new Set([
24
24
  // --- Intentional local daemon-control paths ---
25
25
  "assistant/src/cli/commands/conversations.ts", // CLI wipe talks to runtime directly
26
26
  "clients/shared/Network/DaemonClient.swift",
27
+ "clients/shared/App/Auth/PlatformOAuthService.swift", // comment explaining runtimeUrl vs platformUrl
27
28
  "clients/macos/vellum-assistant/App/AppDelegate.swift",
28
29
  "clients/macos/vellum-assistant/Features/Settings/SettingsConnectTab.swift",
29
30
  ".claude/skills/update/SKILL.md", // daemon health check script
@@ -268,13 +268,9 @@ describe("handleConfirmationResponse canonical status sync", () => {
268
268
  undefined,
269
269
  { source: "button" },
270
270
  ]);
271
- expect(resolveCanonicalGuardianRequestMock).toHaveBeenCalledWith(
272
- "req-confirm-allow",
273
- "pending",
274
- {
275
- status: "approved",
276
- },
277
- );
271
+ // Canonical status sync is now handled inside Conversation.handleConfirmationResponse,
272
+ // which this test mocks out — so the handler itself no longer calls resolveCanonicalGuardianRequest.
273
+ expect(resolveCanonicalGuardianRequestMock).not.toHaveBeenCalled();
278
274
  expect(resolveMock).toHaveBeenCalledWith("req-confirm-allow");
279
275
  });
280
276
  });