@vellumai/assistant 0.5.1 → 0.5.3

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 (405) hide show
  1. package/ARCHITECTURE.md +163 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/docs/skills.md +100 -0
  5. package/package.json +1 -1
  6. package/src/__tests__/agent-loop.test.ts +111 -0
  7. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  8. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  9. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  10. package/src/__tests__/app-executors.test.ts +1 -291
  11. package/src/__tests__/app-git-history.test.ts +4 -4
  12. package/src/__tests__/app-routes-csp.test.ts +1 -0
  13. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  14. package/src/__tests__/attachments-store.test.ts +169 -21
  15. package/src/__tests__/attachments.test.ts +115 -1
  16. package/src/__tests__/btw-routes.test.ts +1 -0
  17. package/src/__tests__/canonical-guardian-store.test.ts +38 -0
  18. package/src/__tests__/channel-reply-delivery.test.ts +55 -0
  19. package/src/__tests__/checker.test.ts +54 -0
  20. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  21. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  22. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  23. package/src/__tests__/config-schema-cmd.test.ts +68 -21
  24. package/src/__tests__/config-schema.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +156 -5
  26. package/src/__tests__/conversation-agent-loop.test.ts +297 -2
  27. package/src/__tests__/conversation-attachments.test.ts +17 -19
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
  29. package/src/__tests__/conversation-disk-view.test.ts +810 -0
  30. package/src/__tests__/conversation-error.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +551 -0
  32. package/src/__tests__/conversation-fork-route.test.ts +386 -0
  33. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  34. package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
  35. package/src/__tests__/conversation-media-retry.test.ts +8 -2
  36. package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
  37. package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
  38. package/src/__tests__/conversation-queue.test.ts +36 -1
  39. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  40. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  42. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  43. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  44. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  45. package/src/__tests__/conversation-store.test.ts +24 -21
  46. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  47. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  48. package/src/__tests__/conversation-title-service.test.ts +137 -0
  49. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  50. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  51. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  52. package/src/__tests__/conversation-wipe.test.ts +226 -0
  53. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  54. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  55. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  56. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  57. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  58. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  59. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  60. package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
  61. package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
  62. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
  63. package/src/__tests__/diagnostics-export.test.ts +70 -1
  64. package/src/__tests__/first-greeting.test.ts +80 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  66. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  67. package/src/__tests__/history-repair.test.ts +32 -10
  68. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  69. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  70. package/src/__tests__/inline-command-runner.test.ts +311 -0
  71. package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
  72. package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
  73. package/src/__tests__/list-messages-attachments.test.ts +96 -0
  74. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  75. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  76. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  77. package/src/__tests__/media-generate-image.test.ts +47 -94
  78. package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
  79. package/src/__tests__/memory-brief-time.test.ts +285 -0
  80. package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
  81. package/src/__tests__/memory-chunk-archive.test.ts +400 -0
  82. package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
  83. package/src/__tests__/memory-episode-archive.test.ts +370 -0
  84. package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  86. package/src/__tests__/memory-observation-archive.test.ts +375 -0
  87. package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
  88. package/src/__tests__/memory-recall-quality.test.ts +7 -7
  89. package/src/__tests__/memory-reducer-store.test.ts +728 -0
  90. package/src/__tests__/memory-reducer-types.test.ts +699 -0
  91. package/src/__tests__/memory-reducer.test.ts +698 -0
  92. package/src/__tests__/memory-regressions.test.ts +6 -4
  93. package/src/__tests__/memory-simplified-config.test.ts +281 -0
  94. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  95. package/src/__tests__/migration-export-http.test.ts +3 -1
  96. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  97. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  98. package/src/__tests__/mime-builder.test.ts +3 -2
  99. package/src/__tests__/non-member-access-request.test.ts +12 -1
  100. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  101. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  102. package/src/__tests__/oauth-store.test.ts +115 -0
  103. package/src/__tests__/parse-identity-fields.test.ts +129 -0
  104. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  105. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  106. package/src/__tests__/recording-handler.test.ts +17 -0
  107. package/src/__tests__/registry.test.ts +3 -8
  108. package/src/__tests__/relay-server.test.ts +1 -1
  109. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  110. package/src/__tests__/schema-transforms.test.ts +165 -5
  111. package/src/__tests__/server-history-render.test.ts +2 -2
  112. package/src/__tests__/skill-load-inline-command.test.ts +598 -0
  113. package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
  114. package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
  115. package/src/__tests__/skills-transitive-hash.test.ts +333 -0
  116. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  117. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  118. package/src/__tests__/starter-task-flow.test.ts +1 -0
  119. package/src/__tests__/suggestion-routes.test.ts +443 -0
  120. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  121. package/src/__tests__/swarm-recursion.test.ts +1 -0
  122. package/src/__tests__/swarm-tool.test.ts +1 -0
  123. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  124. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  125. package/src/__tests__/top-level-renderer.test.ts +22 -0
  126. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  127. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
  128. package/src/__tests__/web-fetch.test.ts +6 -2
  129. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  130. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  131. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  132. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  133. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  134. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  135. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  136. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  137. package/src/agent/attachments.ts +27 -1
  138. package/src/agent/loop.ts +29 -1
  139. package/src/avatar/traits-png-sync.ts +80 -25
  140. package/src/bundler/app-bundler.ts +4 -4
  141. package/src/calls/call-domain.ts +1 -0
  142. package/src/calls/voice-session-bridge.ts +1 -0
  143. package/src/cli/commands/auth.ts +92 -0
  144. package/src/cli/commands/avatar.ts +7 -6
  145. package/src/cli/commands/config.ts +2 -0
  146. package/src/cli/commands/oauth/providers.ts +29 -0
  147. package/src/cli/program.ts +12 -0
  148. package/src/cli.ts +15 -48
  149. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  150. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  151. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  152. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  153. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  154. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  155. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  156. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  157. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  158. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  159. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  160. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  161. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  162. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  163. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  164. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  165. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  166. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  170. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  171. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  172. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  173. package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
  174. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  175. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  176. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  177. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  178. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  179. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  180. package/src/config/bundled-tool-registry.ts +2 -14
  181. package/src/config/feature-flag-registry.json +24 -0
  182. package/src/config/loader.ts +65 -0
  183. package/src/config/raw-config-utils.ts +58 -0
  184. package/src/config/schema-utils.ts +28 -7
  185. package/src/config/schema.ts +20 -0
  186. package/src/config/schemas/elevenlabs.ts +18 -0
  187. package/src/config/schemas/memory-lifecycle.ts +4 -2
  188. package/src/config/schemas/memory-simplified.ts +101 -0
  189. package/src/config/schemas/memory-storage.ts +1 -1
  190. package/src/config/schemas/memory.ts +4 -0
  191. package/src/config/schemas/services.ts +8 -6
  192. package/src/config/skills.ts +50 -4
  193. package/src/contacts/contact-store.ts +13 -6
  194. package/src/contacts/contacts-write.ts +0 -1
  195. package/src/context/window-manager.ts +13 -2
  196. package/src/daemon/conversation-agent-loop-handlers.ts +54 -8
  197. package/src/daemon/conversation-agent-loop.ts +127 -20
  198. package/src/daemon/conversation-attachments.ts +18 -36
  199. package/src/daemon/conversation-error.ts +2 -1
  200. package/src/daemon/conversation-history.ts +18 -4
  201. package/src/daemon/conversation-lifecycle.ts +50 -16
  202. package/src/daemon/conversation-messaging.ts +70 -26
  203. package/src/daemon/conversation-process.ts +58 -34
  204. package/src/daemon/conversation-runtime-assembly.ts +22 -38
  205. package/src/daemon/conversation-slash.ts +121 -256
  206. package/src/daemon/conversation-surfaces.ts +170 -24
  207. package/src/daemon/conversation-tool-setup.ts +0 -6
  208. package/src/daemon/conversation-workspace.ts +21 -1
  209. package/src/daemon/conversation.ts +69 -30
  210. package/src/daemon/first-greeting.ts +35 -0
  211. package/src/daemon/handlers/config-embeddings.ts +156 -0
  212. package/src/daemon/handlers/config-model.ts +62 -26
  213. package/src/daemon/handlers/conversations.ts +0 -23
  214. package/src/daemon/handlers/identity.ts +12 -1
  215. package/src/daemon/handlers/recording.ts +26 -21
  216. package/src/daemon/host-cu-proxy.ts +2 -2
  217. package/src/daemon/lifecycle.ts +115 -65
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/conversations.ts +18 -0
  220. package/src/daemon/message-types/messages.ts +1 -0
  221. package/src/daemon/message-types/shared.ts +2 -0
  222. package/src/daemon/message-types/surfaces.ts +2 -0
  223. package/src/daemon/message-types/upgrades.ts +23 -0
  224. package/src/daemon/server.ts +83 -12
  225. package/src/daemon/shutdown-handlers.ts +8 -5
  226. package/src/daemon/startup-error.ts +9 -0
  227. package/src/daemon/tool-side-effects.ts +11 -28
  228. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  229. package/src/followups/followup-store.ts +47 -1
  230. package/src/instrument.ts +0 -4
  231. package/src/media/app-icon-generator.ts +2 -2
  232. package/src/memory/app-git-service.ts +28 -16
  233. package/src/memory/app-store.ts +230 -41
  234. package/src/memory/archive-store.ts +400 -0
  235. package/src/memory/attachments-store.ts +558 -130
  236. package/src/memory/brief-formatting.ts +33 -0
  237. package/src/memory/brief-open-loops.ts +266 -0
  238. package/src/memory/brief-time.ts +161 -0
  239. package/src/memory/brief.ts +75 -0
  240. package/src/memory/conversation-attention-store.ts +70 -0
  241. package/src/memory/conversation-crud.ts +591 -8
  242. package/src/memory/conversation-directories.ts +125 -0
  243. package/src/memory/conversation-disk-view.ts +390 -0
  244. package/src/memory/conversation-key-store.ts +17 -5
  245. package/src/memory/conversation-queries.ts +5 -1
  246. package/src/memory/conversation-title-service.ts +21 -49
  247. package/src/memory/db-init.ts +40 -0
  248. package/src/memory/embedding-backend.ts +42 -53
  249. package/src/memory/embedding-gemini.test.ts +4 -4
  250. package/src/memory/embedding-local.ts +1 -3
  251. package/src/memory/embedding-ollama.ts +1 -3
  252. package/src/memory/embedding-openai.ts +1 -3
  253. package/src/memory/indexer.ts +114 -21
  254. package/src/memory/items-extractor.ts +42 -13
  255. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  256. package/src/memory/job-handlers/embedding.test.ts +2 -4
  257. package/src/memory/job-handlers/embedding.ts +83 -0
  258. package/src/memory/job-utils.ts +1 -1
  259. package/src/memory/jobs-store.ts +6 -0
  260. package/src/memory/jobs-worker.ts +12 -0
  261. package/src/memory/llm-request-log-store.ts +100 -1
  262. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  263. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  264. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  265. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  266. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  267. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  268. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  269. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  270. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  271. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  272. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  273. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  274. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  275. package/src/memory/migrations/185-memory-brief-state.ts +52 -0
  276. package/src/memory/migrations/186-memory-archive.ts +109 -0
  277. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
  278. package/src/memory/migrations/index.ts +10 -0
  279. package/src/memory/migrations/registry.ts +13 -0
  280. package/src/memory/qdrant-client.ts +23 -4
  281. package/src/memory/reducer-store.ts +271 -0
  282. package/src/memory/reducer-types.ts +99 -0
  283. package/src/memory/reducer.ts +453 -0
  284. package/src/memory/retriever.test.ts +601 -2
  285. package/src/memory/retriever.ts +85 -9
  286. package/src/memory/schema/conversations.ts +9 -0
  287. package/src/memory/schema/index.ts +2 -0
  288. package/src/memory/schema/infrastructure.ts +13 -7
  289. package/src/memory/schema/memory-archive.ts +121 -0
  290. package/src/memory/schema/memory-brief.ts +55 -0
  291. package/src/memory/schema/oauth.ts +6 -0
  292. package/src/memory/search/semantic.ts +17 -4
  293. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  294. package/src/notifications/copy-composer.ts +26 -0
  295. package/src/notifications/decision-engine.ts +14 -1
  296. package/src/notifications/emit-signal.ts +1 -1
  297. package/src/notifications/signal.ts +36 -0
  298. package/src/oauth/byo-connection.test.ts +1 -45
  299. package/src/oauth/byo-connection.ts +2 -8
  300. package/src/oauth/connect-orchestrator.ts +15 -11
  301. package/src/oauth/connection-resolver.test.ts +191 -0
  302. package/src/oauth/connection-resolver.ts +66 -38
  303. package/src/oauth/connection.ts +0 -1
  304. package/src/oauth/oauth-store.ts +99 -47
  305. package/src/oauth/platform-connection.test.ts +0 -1
  306. package/src/oauth/platform-connection.ts +11 -3
  307. package/src/oauth/seed-providers.ts +78 -3
  308. package/src/oauth/token-persistence.ts +16 -10
  309. package/src/permissions/checker.ts +160 -14
  310. package/src/permissions/defaults.ts +14 -0
  311. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  312. package/src/providers/anthropic/client.ts +8 -1
  313. package/src/providers/failover.ts +4 -1
  314. package/src/providers/gemini/client.ts +50 -0
  315. package/src/providers/model-catalog.ts +92 -0
  316. package/src/providers/model-intents.ts +29 -20
  317. package/src/providers/openai/client.ts +49 -0
  318. package/src/providers/types.ts +2 -0
  319. package/src/runtime/access-request-helper.ts +16 -7
  320. package/src/runtime/auth/credential-service.ts +3 -1
  321. package/src/runtime/auth/route-policy.ts +14 -1
  322. package/src/runtime/btw-sidechain.ts +101 -0
  323. package/src/runtime/channel-reply-delivery.ts +17 -1
  324. package/src/runtime/http-router.ts +3 -1
  325. package/src/runtime/http-server.ts +196 -141
  326. package/src/runtime/http-types.ts +1 -0
  327. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  328. package/src/runtime/routes/access-request-decision.ts +41 -0
  329. package/src/runtime/routes/app-management-routes.ts +6 -3
  330. package/src/runtime/routes/app-routes.ts +7 -3
  331. package/src/runtime/routes/approval-routes.ts +1 -0
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  333. package/src/runtime/routes/attachment-routes.ts +45 -15
  334. package/src/runtime/routes/btw-routes.ts +21 -61
  335. package/src/runtime/routes/conversation-management-routes.ts +74 -0
  336. package/src/runtime/routes/conversation-query-routes.ts +187 -10
  337. package/src/runtime/routes/conversation-routes.ts +269 -28
  338. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  339. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  340. package/src/runtime/routes/identity-routes.ts +2 -35
  341. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  342. package/src/runtime/routes/llm-context-normalization.ts +1212 -0
  343. package/src/runtime/routes/log-export-routes.ts +3 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  345. package/src/runtime/routes/memory-item-routes.ts +94 -5
  346. package/src/runtime/routes/migration-routes.ts +4 -1
  347. package/src/runtime/routes/oauth-apps.ts +291 -0
  348. package/src/runtime/routes/secret-routes.ts +30 -1
  349. package/src/runtime/routes/settings-routes.ts +14 -0
  350. package/src/runtime/routes/surface-action-routes.ts +68 -1
  351. package/src/runtime/routes/trace-event-routes.ts +4 -1
  352. package/src/schedule/schedule-store.ts +30 -21
  353. package/src/security/secure-keys.ts +21 -0
  354. package/src/signals/bash.ts +1 -1
  355. package/src/skills/inline-command-expansions.ts +204 -0
  356. package/src/skills/inline-command-render.ts +127 -0
  357. package/src/skills/inline-command-runner.ts +242 -0
  358. package/src/skills/transitive-version-hash.ts +88 -0
  359. package/src/swarm/backend-claude-code.ts +3 -6
  360. package/src/tasks/task-store.ts +43 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  362. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  363. package/src/tools/AGENTS.md +6 -10
  364. package/src/tools/apps/executors.ts +17 -232
  365. package/src/tools/claude-code/claude-code.ts +2 -3
  366. package/src/tools/credentials/vault.ts +7 -12
  367. package/src/tools/host-filesystem/read.ts +13 -10
  368. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  369. package/src/tools/permission-checker.ts +8 -1
  370. package/src/tools/schedule/list.ts +2 -7
  371. package/src/tools/schema-transforms.ts +5 -0
  372. package/src/tools/shared/filesystem/format-diff.ts +2 -7
  373. package/src/tools/skills/execute.ts +1 -1
  374. package/src/tools/skills/load.ts +140 -6
  375. package/src/tools/tool-manifest.ts +0 -6
  376. package/src/tools/ui-surface/definitions.ts +2 -2
  377. package/src/util/device-id.ts +28 -5
  378. package/src/util/platform.ts +24 -0
  379. package/src/util/pricing.ts +1 -0
  380. package/src/util/retry.ts +1 -3
  381. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  382. package/src/workspace/migrations/006-services-config.ts +5 -0
  383. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  384. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  385. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  386. package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
  387. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  388. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  389. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  390. package/src/workspace/migrations/registry.ts +11 -1
  391. package/src/workspace/top-level-renderer.ts +12 -0
  392. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  393. package/src/__tests__/asset-search-tool.test.ts +0 -536
  394. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  395. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  396. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  397. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  398. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  399. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  400. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  401. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  402. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  403. package/src/daemon/media-visibility-policy.ts +0 -59
  404. package/src/tools/assets/materialize.ts +0 -248
  405. package/src/tools/assets/search.ts +0 -400
@@ -1,6 +1,10 @@
1
+ import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
1
4
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
5
 
3
6
  import type { AgentEvent } from "../agent/loop.js";
7
+ import { getConversationDirName } from "../memory/conversation-disk-view.js";
4
8
  import type { Message, ProviderResponse } from "../providers/types.js";
5
9
 
6
10
  // ---------------------------------------------------------------------------
@@ -79,6 +83,7 @@ mock.module("../memory/conversation-crud.js", () => ({
79
83
  getMessages: () => [],
80
84
  getConversation: () => ({
81
85
  id: "conv-1",
86
+ createdAt: Date.parse("2026-03-19T12:00:00.000Z"),
82
87
  contextSummary: null,
83
88
  contextCompactedMessageCount: 0,
84
89
  contextCompactedAt: null,
@@ -175,7 +180,7 @@ import { Conversation } from "../daemon/conversation.js";
175
180
  // Helpers
176
181
  // ---------------------------------------------------------------------------
177
182
 
178
- function makeConversation(): Conversation {
183
+ function makeConversation(workingDir = "/tmp"): Conversation {
179
184
  const provider = {
180
185
  name: "mock",
181
186
  async sendMessage(): Promise<ProviderResponse> {
@@ -193,10 +198,17 @@ function makeConversation(): Conversation {
193
198
  "system prompt",
194
199
  4096,
195
200
  () => {},
196
- "/tmp",
201
+ workingDir,
197
202
  );
198
203
  }
199
204
 
205
+ const conversationDirName = getConversationDirName(
206
+ "conv-1",
207
+ Date.parse("2026-03-19T12:00:00.000Z"),
208
+ );
209
+ const conversationPath = `conversations/${conversationDirName}/`;
210
+ const conversationAttachmentsPath = `${conversationPath}attachments/`;
211
+
200
212
  // ---------------------------------------------------------------------------
201
213
  // Tests
202
214
  // ---------------------------------------------------------------------------
@@ -224,6 +236,12 @@ describe("Conversation workspace cache state", () => {
224
236
  expect(conversation.getWorkspaceTopLevelContext()!).toContain(
225
237
  "</workspace_top_level>",
226
238
  );
239
+ expect(conversation.getWorkspaceTopLevelContext()!).toContain(
240
+ `Current conversation folder: ${conversationPath}`,
241
+ );
242
+ expect(conversation.getWorkspaceTopLevelContext()!).toContain(
243
+ `Attachment files: ${conversationAttachmentsPath}`,
244
+ );
227
245
  });
228
246
 
229
247
  test("refreshWorkspaceTopLevelContextIfNeeded no-ops when not dirty and cache exists", () => {
@@ -257,4 +275,28 @@ describe("Conversation workspace cache state", () => {
257
275
  );
258
276
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
259
277
  });
278
+
279
+ test("workspace hints follow the resolved legacy directory when canonical is absent", () => {
280
+ const workspaceRoot = mkdtempSync(
281
+ join(tmpdir(), "conversation-workspace-cache-state-"),
282
+ );
283
+ const legacyDirName = `conv-1_2026-03-19T12-00-00.000Z`;
284
+ mkdirSync(join(workspaceRoot, "conversations", legacyDirName), {
285
+ recursive: true,
286
+ });
287
+
288
+ try {
289
+ const tempConversation = makeConversation(workspaceRoot);
290
+ tempConversation.refreshWorkspaceTopLevelContextIfNeeded();
291
+
292
+ expect(tempConversation.getWorkspaceTopLevelContext()!).toContain(
293
+ `Current conversation folder: conversations/${legacyDirName}/`,
294
+ );
295
+ expect(tempConversation.getWorkspaceTopLevelContext()!).toContain(
296
+ `Attachment files: conversations/${legacyDirName}/attachments/`,
297
+ );
298
+ } finally {
299
+ rmSync(workspaceRoot, { recursive: true, force: true });
300
+ }
301
+ });
260
302
  });
@@ -1,6 +1,7 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  import type { AgentEvent } from "../agent/loop.js";
4
+ import { getConversationDirName } from "../memory/conversation-disk-view.js";
4
5
  import type { Message, ProviderResponse } from "../providers/types.js";
5
6
 
6
7
  // ---------------------------------------------------------------------------
@@ -115,6 +116,7 @@ mock.module("../memory/conversation-crud.js", () => ({
115
116
  getMessages: () => [],
116
117
  getConversation: () => ({
117
118
  id: "conv-1",
119
+ createdAt: Date.parse("2026-03-19T12:00:00.000Z"),
118
120
  contextSummary: null,
119
121
  contextCompactedMessageCount: 0,
120
122
  contextCompactedAt: null,
@@ -277,6 +279,13 @@ function messageText(message: Message): string {
277
279
  .join("\n");
278
280
  }
279
281
 
282
+ const conversationDirName = getConversationDirName(
283
+ "conv-1",
284
+ Date.parse("2026-03-19T12:00:00.000Z"),
285
+ );
286
+ const conversationPath = `conversations/${conversationDirName}/`;
287
+ const conversationAttachmentsPath = `${conversationPath}attachments/`;
288
+
280
289
  // ---------------------------------------------------------------------------
281
290
  // Tests
282
291
  // ---------------------------------------------------------------------------
@@ -312,6 +321,8 @@ describe("Conversation workspace injection", () => {
312
321
  const runtimeUser = runCalls[0][runCalls[0].length - 1];
313
322
  const text = messageText(runtimeUser);
314
323
  expect(text).toContain("Root: /tmp");
324
+ expect(text).toContain(`Current conversation folder: ${conversationPath}`);
325
+ expect(text).toContain(`Attachment files: ${conversationAttachmentsPath}`);
315
326
  });
316
327
 
317
328
  test("workspace context is prepended before user text", async () => {
@@ -251,6 +251,9 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
251
251
  "tools/claude-code/claude-code.ts", // Claude Code tool API key lookup
252
252
  "workspace/migrations/006-services-config.ts", // services config migration reads provider API keys
253
253
  "cli/commands/avatar.ts", // CLI avatar generation API key lookup
254
+ "config/bundled-skills/slack/tools/shared.ts", // Slack skill bot token lookup
255
+ "daemon/conversation-process.ts", // masked provider key display
256
+ "daemon/handlers/config-model.ts", // masked provider key display
254
257
  ]);
255
258
 
256
259
  const thisDir = dirname(fileURLToPath(import.meta.url));
@@ -102,13 +102,10 @@ mock.module("../daemon/handlers/config-slack-channel.js", () => ({
102
102
  slackChannelConfigCalls.push({ botToken, appToken });
103
103
 
104
104
  const { credentialKey } = await import("../security/credential-key.js");
105
- const {
106
- getSecureKeyAsync,
107
- setSecureKeyAsync,
108
- } = await import("../security/secure-keys.js");
109
- const { upsertCredentialMetadata } = await import(
110
- "../tools/credentials/metadata-store.js"
111
- );
105
+ const { getSecureKeyAsync, setSecureKeyAsync } =
106
+ await import("../security/secure-keys.js");
107
+ const { upsertCredentialMetadata } =
108
+ await import("../tools/credentials/metadata-store.js");
112
109
 
113
110
  const hasExistingBotToken = !!(await getSecureKeyAsync(
114
111
  credentialKey("slack_channel", "bot_token"),
@@ -646,9 +643,7 @@ describe("credential_store tool — prompt action", () => {
646
643
  );
647
644
  expect(appResult.isError).toBe(false);
648
645
  expect(manualConnectionStore["slack_channel"]).toBeUndefined();
649
- expect(slackChannelConfigCalls).toEqual([
650
- { appToken: "xapp-test-token" },
651
- ]);
646
+ expect(slackChannelConfigCalls).toEqual([{ appToken: "xapp-test-token" }]);
652
647
  expect(appResult.content).toContain("connection incomplete");
653
648
 
654
649
  const botResult = await credentialStoreTool.execute(
@@ -30,6 +30,7 @@ function buildMockContext(
30
30
  lastSurfaceAction: new Map(),
31
31
  surfaceState: new Map(),
32
32
  surfaceUndoStacks: new Map(),
33
+ accumulatedSurfaceState: new Map(),
33
34
  surfaceActionRequestIds: new Set(),
34
35
  currentTurnSurfaces: [],
35
36
  hostCuProxy,
@@ -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
+ });