@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
@@ -287,7 +287,7 @@ describe("Memory Recall Quality", () => {
287
287
  test("preferences are recalled when querying about user preferences", async () => {
288
288
  const db = getDb();
289
289
  const now = 1_700_000_000_000;
290
- insertConversation(db, "conv-pref", now);
290
+ insertConversation(db, "conv-pref", now, 3);
291
291
  insertMessage(
292
292
  db,
293
293
  "msg-pref-1",
@@ -408,7 +408,7 @@ describe("Memory Recall Quality", () => {
408
408
  test("high-importance preferences outrank low-importance facts in recall", async () => {
409
409
  const db = getDb();
410
410
  const now = 1_700_000_100_000;
411
- insertConversation(db, "conv-rank", now);
411
+ insertConversation(db, "conv-rank", now, 2);
412
412
 
413
413
  // High-importance preference
414
414
  insertMessage(
@@ -692,7 +692,7 @@ describe("Memory Recall Quality", () => {
692
692
  const db = getDb();
693
693
  const now = Date.now();
694
694
  const oneMonthAgo = now - 30 * 24 * 60 * 60 * 1000;
695
- insertConversation(db, "conv-stale", now);
695
+ insertConversation(db, "conv-stale", now, 2);
696
696
 
697
697
  // Recent mention
698
698
  insertMessage(
@@ -774,7 +774,7 @@ describe("Memory Recall Quality", () => {
774
774
  test("frequently accessed items surface via recency search when seeded with segments", async () => {
775
775
  const db = getDb();
776
776
  const now = 1_700_000_400_000;
777
- insertConversation(db, "conv-access", now);
777
+ insertConversation(db, "conv-access", now, 2);
778
778
 
779
779
  // Frequently accessed item with segment
780
780
  insertMessage(
@@ -885,7 +885,7 @@ describe("Memory Recall Quality", () => {
885
885
  test("recency search surfaces segments when hybrid search is unavailable", async () => {
886
886
  const db = getDb();
887
887
  const now = 1_700_000_500_000;
888
- insertConversation(db, "conv-multi", now);
888
+ insertConversation(db, "conv-multi", now, 1);
889
889
 
890
890
  // Segment (recency source)
891
891
  insertMessage(
@@ -894,7 +894,10 @@ describe("partial failure scenarios", () => {
894
894
  const blockerWorkspace = join(testDir, "blocker-workspace");
895
895
  mkdirSync(blockerWorkspace, { recursive: true });
896
896
  // Create "data" as a regular file — mkdirSync("data/db") will fail
897
- writeFileSync(join(blockerWorkspace, "data"), "I am a file, not a directory");
897
+ writeFileSync(
898
+ join(blockerWorkspace, "data"),
899
+ "I am a file, not a directory",
900
+ );
898
901
 
899
902
  const resolver = new DefaultPathResolver(undefined, blockerWorkspace);
900
903
  const result = commitImport({
@@ -299,7 +299,9 @@ describe("export data population", () => {
299
299
  const archiveData = new Uint8Array(await res.arrayBuffer());
300
300
  const entries = parseTarEntries(archiveData);
301
301
 
302
- const dbEntry = entries.find((e) => e.name === "workspace/data/db/assistant.db");
302
+ const dbEntry = entries.find(
303
+ (e) => e.name === "workspace/data/db/assistant.db",
304
+ );
303
305
  expect(dbEntry).toBeDefined();
304
306
  expect(dbEntry!.data.length).toBe(SQLITE_HEADER.length);
305
307
  // Verify the exported data matches the test fixture exactly
@@ -671,7 +671,12 @@ describe("commitImport", () => {
671
671
  test("creates parent directories if they do not exist", () => {
672
672
  // Use a workspace that does not exist yet
673
673
  const nonexistentWorkspace = join(testDir, "new-workspace");
674
- const expectedDbPath = join(nonexistentWorkspace, "data", "db", "assistant.db");
674
+ const expectedDbPath = join(
675
+ nonexistentWorkspace,
676
+ "data",
677
+ "db",
678
+ "assistant.db",
679
+ );
675
680
 
676
681
  const dbData = new Uint8Array([0x01, 0x02, 0x03]);
677
682
  const vbundle = createValidVBundle([
@@ -836,7 +841,11 @@ describe("commitImport — workspace clearing", () => {
836
841
  { path: "hooks/new-hook/hook.sh", data: hookData },
837
842
  ]);
838
843
 
839
- const resolver = new DefaultPathResolver(undefined, testDir, externalHooksDir);
844
+ const resolver = new DefaultPathResolver(
845
+ undefined,
846
+ testDir,
847
+ externalHooksDir,
848
+ );
840
849
  const result = commitImport({
841
850
  archiveData: vbundle,
842
851
  pathResolver: resolver,
@@ -847,7 +856,9 @@ describe("commitImport — workspace clearing", () => {
847
856
  if (!result.ok) return;
848
857
 
849
858
  // Hook written to the external hooks dir, not workspace/hooks/
850
- expect(existsSync(join(externalHooksDir, "new-hook", "hook.sh"))).toBe(true);
859
+ expect(existsSync(join(externalHooksDir, "new-hook", "hook.sh"))).toBe(
860
+ true,
861
+ );
851
862
  expect(
852
863
  readFileSync(join(externalHooksDir, "new-hook", "hook.sh"), "utf8"),
853
864
  ).toBe("#!/bin/sh\necho new");
@@ -864,7 +875,10 @@ describe("commitImport — workspace clearing", () => {
864
875
  );
865
876
 
866
877
  const vbundle = createValidVBundle([
867
- { path: "skills/new-skill/SKILL.md", data: new TextEncoder().encode("new") },
878
+ {
879
+ path: "skills/new-skill/SKILL.md",
880
+ data: new TextEncoder().encode("new"),
881
+ },
868
882
  ]);
869
883
 
870
884
  const resolver = new DefaultPathResolver(undefined, testDir);
@@ -743,9 +743,7 @@ describe("DefaultPathResolver", () => {
743
743
  undefined,
744
744
  "/home/user/.vellum/workspace",
745
745
  );
746
- expect(
747
- resolver.resolve("skills/../../../.ssh/authorized_keys"),
748
- ).toBeNull();
746
+ expect(resolver.resolve("skills/../../../.ssh/authorized_keys")).toBeNull();
749
747
  });
750
748
 
751
749
  test("returns null for skills paths when workspaceDir is not provided", () => {
@@ -98,7 +98,9 @@ describe("buildMultipartMime", () => {
98
98
  "base64",
99
99
  ).toString("utf-8");
100
100
 
101
- expect(decoded).toContain("To: victim@example.com Bcc: attacker@example.com");
101
+ expect(decoded).toContain(
102
+ "To: victim@example.com Bcc: attacker@example.com",
103
+ );
102
104
  expect(decoded).toContain("Subject: Fwd: Hello Cc: attacker@example.com");
103
105
  expect(decoded).toContain("Cc: team@example.com X-Injected: yes");
104
106
  expect(decoded).toContain("Bcc: audit@example.com X-Another: value");
@@ -108,5 +110,4 @@ describe("buildMultipartMime", () => {
108
110
  expect(decoded).not.toContain("\r\nBcc: attacker@example.com");
109
111
  expect(decoded).not.toContain("\r\nCc: attacker@example.com");
110
112
  });
111
-
112
113
  });
@@ -624,6 +624,17 @@ describe("access-request-helper unit tests", () => {
624
624
  });
625
625
 
626
626
  test("notifyGuardianOfAccessRequest skips vellum fallback for same-channel-only routing (telegram)", async () => {
627
+ // Set up a telegram guardian binding with the anchor principal so
628
+ // guardianResolutionSource resolves to "source-channel-contact" and
629
+ // sameChannelOnly is true.
630
+ createGuardianBinding({
631
+ channel: "telegram",
632
+ guardianExternalUserId: "guardian-user-456",
633
+ guardianDeliveryChatId: "guardian-chat-456",
634
+ guardianPrincipalId: anchorPrincipalId,
635
+ verifiedVia: "test",
636
+ });
637
+
627
638
  mockEmitResult = {
628
639
  signalId: "sig-no-vellum",
629
640
  deduplicated: false,
@@ -657,7 +668,7 @@ describe("access-request-helper unit tests", () => {
657
668
  (d) => d.destinationChannel === "telegram",
658
669
  );
659
670
 
660
- // Same-channel routing skips vellum delivery entirely no fallback record
671
+ // Guardian IS verified on telegram sameChannelOnly, no vellum fallback
661
672
  expect(vellum).toBeUndefined();
662
673
  expect(telegram).toBeDefined();
663
674
  expect(telegram!.destinationChatId).toBe("guardian-chat-456");
@@ -185,6 +185,58 @@ describe("identity context in notification decision engine", () => {
185
185
  expect(capturedSystemPrompt).not.toContain("</assistant-identity>");
186
186
  });
187
187
 
188
+ test("large identity context is truncated in system prompt", async () => {
189
+ // Create an identity context that exceeds the 2000-char budget
190
+ mockIdentityContext = "A".repeat(3000);
191
+
192
+ configuredProvider = {
193
+ sendMessage: async (
194
+ _messages: unknown,
195
+ _tools: unknown,
196
+ systemPrompt: unknown,
197
+ ) => {
198
+ capturedSystemPrompt = systemPrompt as string;
199
+ return { content: [] };
200
+ },
201
+ };
202
+ extractedToolUse = {
203
+ name: "record_notification_decision",
204
+ input: {
205
+ shouldNotify: true,
206
+ selectedChannels: ["vellum"],
207
+ reasoningSummary: "LLM decision with truncated identity",
208
+ renderedCopy: {
209
+ vellum: {
210
+ title: "Guardian Question",
211
+ body: "What is the gate code?",
212
+ },
213
+ },
214
+ dedupeKey: "identity-truncated-test",
215
+ confidence: 0.9,
216
+ },
217
+ };
218
+
219
+ const signal = makeSignal();
220
+ await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
221
+
222
+ expect(capturedSystemPrompt).toBeDefined();
223
+ expect(capturedSystemPrompt).toContain("<assistant-identity>");
224
+ // The identity block should exist but should NOT contain the full 3000-char string
225
+ expect(capturedSystemPrompt).not.toContain("A".repeat(3000));
226
+ // It should contain the truncation marker
227
+ expect(capturedSystemPrompt).toContain("…[truncated]");
228
+ // The identity content within the block should be at most 2000 chars
229
+ const identityMatch = capturedSystemPrompt!.match(
230
+ /<assistant-identity>([\s\S]*?)<\/assistant-identity>/,
231
+ );
232
+ expect(identityMatch).toBeTruthy();
233
+ // The identity block includes the instruction text + the truncated context.
234
+ // Verify the raw identity portion is bounded.
235
+ const identityBlock = identityMatch![1];
236
+ expect(identityBlock).toContain("…[truncated]");
237
+ expect(identityBlock).not.toContain("A".repeat(2001));
238
+ });
239
+
188
240
  test("fallback path does not include identity context", async () => {
189
241
  mockIdentityContext = "I am Jarvis, a helpful assistant";
190
242
 
@@ -0,0 +1,103 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ const mockGetApp = mock((_appId: string) => ({
4
+ id: "app-1",
5
+ providerKey: "google",
6
+ clientId: "client-1",
7
+ }));
8
+
9
+ const mockListConnections = mock(() => [
10
+ {
11
+ id: "conn-1",
12
+ providerKey: "google",
13
+ accountInfo: "{\"email\":\"alice@example.com\"}",
14
+ grantedScopes: '["email","profile"]',
15
+ status: "active",
16
+ hasRefreshToken: 1,
17
+ expiresAt: 1735689600000,
18
+ createdAt: 1735689500000,
19
+ updatedAt: 1735689550000,
20
+ },
21
+ {
22
+ id: "conn-2",
23
+ providerKey: "google",
24
+ accountInfo: null,
25
+ grantedScopes: [],
26
+ status: "active",
27
+ hasRefreshToken: 0,
28
+ expiresAt: null,
29
+ createdAt: 1735689601000,
30
+ updatedAt: 1735689602000,
31
+ },
32
+ ]);
33
+
34
+ mock.module("../oauth/oauth-store.js", () => ({
35
+ deleteApp: mock(() => Promise.resolve()),
36
+ disconnectOAuthProvider: mock(() => Promise.resolve()),
37
+ getApp: mockGetApp,
38
+ getAppClientSecret: mock(() => Promise.resolve(undefined)),
39
+ getConnection: mock(() => undefined),
40
+ getProvider: mock(() => undefined),
41
+ listApps: mock(() => []),
42
+ listConnections: mockListConnections,
43
+ upsertApp: mock(() =>
44
+ Promise.resolve({
45
+ id: "app-1",
46
+ providerKey: "google",
47
+ clientId: "client-1",
48
+ createdAt: 1735689500000,
49
+ updatedAt: 1735689550000,
50
+ }),
51
+ ),
52
+ }));
53
+
54
+ mock.module("../oauth/connect-orchestrator.js", () => ({
55
+ orchestrateOAuthConnect: mock(() =>
56
+ Promise.resolve({
57
+ success: true,
58
+ deferred: false,
59
+ grantedScopes: [],
60
+ accountInfo: null,
61
+ refreshTokenPresent: false,
62
+ }),
63
+ ),
64
+ }));
65
+
66
+ import { oauthAppsRouteDefinitions } from "../runtime/routes/oauth-apps.js";
67
+
68
+ const routes = oauthAppsRouteDefinitions();
69
+
70
+ function getRoute(method: string, endpoint: string) {
71
+ const route = routes.find(
72
+ (r) => r.method === method && r.endpoint === endpoint,
73
+ );
74
+ if (!route) throw new Error(`Route not found: ${method} ${endpoint}`);
75
+ return route;
76
+ }
77
+
78
+ describe("GET /v1/oauth/apps/:appId/connections", () => {
79
+ test("normalizes granted_scopes and has_refresh_token", async () => {
80
+ const req = new Request("http://localhost/v1/oauth/apps/app-1/connections");
81
+ const url = new URL(req.url);
82
+ const res = await getRoute("GET", "oauth/apps/:appId/connections").handler({
83
+ req,
84
+ url,
85
+ server: null as never,
86
+ authContext: null as never,
87
+ params: { appId: "app-1" },
88
+ });
89
+
90
+ expect(res.status).toBe(200);
91
+ const body = (await res.json()) as {
92
+ connections: Array<{
93
+ granted_scopes: unknown;
94
+ has_refresh_token: unknown;
95
+ }>;
96
+ };
97
+
98
+ expect(body.connections[0]?.granted_scopes).toEqual(["email", "profile"]);
99
+ expect(body.connections[0]?.has_refresh_token).toBe(true);
100
+ expect(body.connections[1]?.granted_scopes).toEqual([]);
101
+ expect(body.connections[1]?.has_refresh_token).toBe(false);
102
+ });
103
+ });
@@ -46,6 +46,7 @@ import {
46
46
  deleteApp,
47
47
  deleteConnection,
48
48
  disconnectOAuthProvider,
49
+ getActiveConnection,
49
50
  getApp,
50
51
  getAppByProviderAndClientId,
51
52
  getConnection,
@@ -631,6 +632,120 @@ describe("connection operations", () => {
631
632
  });
632
633
  });
633
634
 
635
+ describe("getActiveConnection", () => {
636
+ test("returns the most recent active connection with no filters", async () => {
637
+ const app = await createTestApp("github", "client-1");
638
+
639
+ createConnection({
640
+ oauthAppId: app.id,
641
+ providerKey: "github",
642
+ grantedScopes: ["repo"],
643
+ hasRefreshToken: false,
644
+ createdAt: 1000,
645
+ });
646
+
647
+ const conn2 = createConnection({
648
+ oauthAppId: app.id,
649
+ providerKey: "github",
650
+ grantedScopes: ["repo", "user"],
651
+ hasRefreshToken: true,
652
+ createdAt: 2000,
653
+ });
654
+
655
+ const result = getActiveConnection("github");
656
+ expect(result).toBeDefined();
657
+ expect(result!.id).toBe(conn2.id);
658
+ });
659
+
660
+ test("narrows by account when provided", async () => {
661
+ const app = await createTestApp("github", "client-1");
662
+
663
+ const conn1 = createConnection({
664
+ oauthAppId: app.id,
665
+ providerKey: "github",
666
+ accountInfo: "user1@example.com",
667
+ grantedScopes: ["repo"],
668
+ hasRefreshToken: false,
669
+ createdAt: 1000,
670
+ });
671
+
672
+ createConnection({
673
+ oauthAppId: app.id,
674
+ providerKey: "github",
675
+ accountInfo: "user2@example.com",
676
+ grantedScopes: ["repo"],
677
+ hasRefreshToken: false,
678
+ createdAt: 2000,
679
+ });
680
+
681
+ const result = getActiveConnection("github", {
682
+ account: "user1@example.com",
683
+ });
684
+ expect(result).toBeDefined();
685
+ expect(result!.id).toBe(conn1.id);
686
+ });
687
+
688
+ test("narrows by clientId when provided", async () => {
689
+ const app1 = await createTestApp("github", "client-a");
690
+ const app2 = await createTestApp("github", "client-b");
691
+
692
+ const conn1 = createConnection({
693
+ oauthAppId: app1.id,
694
+ providerKey: "github",
695
+ grantedScopes: ["repo"],
696
+ hasRefreshToken: false,
697
+ createdAt: 1000,
698
+ });
699
+
700
+ createConnection({
701
+ oauthAppId: app2.id,
702
+ providerKey: "github",
703
+ grantedScopes: ["repo"],
704
+ hasRefreshToken: false,
705
+ createdAt: 2000,
706
+ });
707
+
708
+ const result = getActiveConnection("github", { clientId: "client-a" });
709
+ expect(result).toBeDefined();
710
+ expect(result!.id).toBe(conn1.id);
711
+ });
712
+
713
+ test("returns undefined when clientId has no matching app", async () => {
714
+ const app = await createTestApp("github", "client-1");
715
+
716
+ createConnection({
717
+ oauthAppId: app.id,
718
+ providerKey: "github",
719
+ grantedScopes: ["repo"],
720
+ hasRefreshToken: false,
721
+ });
722
+
723
+ const result = getActiveConnection("github", {
724
+ clientId: "nonexistent",
725
+ });
726
+ expect(result).toBeUndefined();
727
+ });
728
+
729
+ test("skips revoked connections", async () => {
730
+ const app = await createTestApp("github", "client-1");
731
+
732
+ const conn = createConnection({
733
+ oauthAppId: app.id,
734
+ providerKey: "github",
735
+ grantedScopes: ["repo"],
736
+ hasRefreshToken: false,
737
+ });
738
+ updateConnection(conn.id, { status: "revoked" });
739
+
740
+ const result = getActiveConnection("github");
741
+ expect(result).toBeUndefined();
742
+ });
743
+
744
+ test("returns undefined when no connections exist", () => {
745
+ expect(getActiveConnection("github")).toBeUndefined();
746
+ });
747
+ });
748
+
634
749
  describe("getConnectionByProvider", () => {
635
750
  test("returns the most recent active connection", async () => {
636
751
  const app = await createTestApp("github", "client-1");
@@ -68,9 +68,7 @@ mock.module("../util/retry.js", () => {
68
68
  const causeCode = (error.cause as NodeJS.ErrnoException).code;
69
69
  if (causeCode && retryableCodes.has(causeCode)) return true;
70
70
  }
71
- if (
72
- RETRYABLE_NETWORK_MESSAGE_PATTERNS.some((p) => p.test(error.message))
73
- ) {
71
+ if (RETRYABLE_NETWORK_MESSAGE_PATTERNS.some((p) => p.test(error.message))) {
74
72
  return true;
75
73
  }
76
74
  const cause = error.cause;
@@ -0,0 +1,66 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ mock.module("../util/logger.js", () => ({
4
+ getLogger: () =>
5
+ new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
6
+ }));
7
+
8
+ import { FailoverProvider } from "../providers/failover.js";
9
+ import type {
10
+ Message,
11
+ Provider,
12
+ ProviderResponse,
13
+ } from "../providers/types.js";
14
+ import { ProviderError } from "../util/errors.js";
15
+
16
+ const MESSAGES: Message[] = [
17
+ { role: "user", content: [{ type: "text", text: "Hello" }] },
18
+ ];
19
+
20
+ function successResponse(
21
+ overrides?: Partial<ProviderResponse>,
22
+ ): ProviderResponse {
23
+ return {
24
+ content: [{ type: "text", text: "ok" }],
25
+ model: "test-model",
26
+ usage: { inputTokens: 10, outputTokens: 5 },
27
+ stopReason: "end_turn",
28
+ ...overrides,
29
+ };
30
+ }
31
+
32
+ describe("FailoverProvider actual provider propagation", () => {
33
+ test("stamps the winning provider when failover uses a fallback", async () => {
34
+ const primary: Provider = {
35
+ name: "openrouter",
36
+ async sendMessage() {
37
+ throw new ProviderError("down", "openrouter", 500);
38
+ },
39
+ };
40
+ const secondary: Provider = {
41
+ name: "fireworks",
42
+ async sendMessage() {
43
+ return successResponse();
44
+ },
45
+ };
46
+
47
+ const provider = new FailoverProvider([primary, secondary]);
48
+ const response = await provider.sendMessage(MESSAGES);
49
+
50
+ expect(response.actualProvider).toBe("fireworks");
51
+ });
52
+
53
+ test("preserves an inner provider's actual provider when already set", async () => {
54
+ const inner: Provider = {
55
+ name: "retry-wrapper",
56
+ async sendMessage() {
57
+ return successResponse({ actualProvider: "anthropic" });
58
+ },
59
+ };
60
+
61
+ const provider = new FailoverProvider([inner]);
62
+ const response = await provider.sendMessage(MESSAGES);
63
+
64
+ expect(response.actualProvider).toBe("anthropic");
65
+ });
66
+ });
@@ -81,6 +81,23 @@ const mockAttachments: Array<{
81
81
  let mockAttachmentIdCounter = 0;
82
82
 
83
83
  mock.module("../memory/attachments-store.js", () => ({
84
+ attachFileBackedAttachmentToMessage: (
85
+ _messageId: string,
86
+ _position: number,
87
+ filename: string,
88
+ mimeType: string,
89
+ _filePath: string,
90
+ sizeBytes: number,
91
+ ) => {
92
+ const att = {
93
+ id: `att-${++mockAttachmentIdCounter}`,
94
+ originalFilename: filename,
95
+ mimeType,
96
+ sizeBytes,
97
+ };
98
+ mockAttachments.push(att);
99
+ return att;
100
+ },
84
101
  uploadFileBackedAttachment: (
85
102
  filename: string,
86
103
  mimeType: string,
@@ -109,7 +109,7 @@ describe("tool registry dynamic-tools tools", () => {
109
109
 
110
110
  describe("tool manifest", () => {
111
111
  test("eager module tool names list contains expected count", () => {
112
- expect(eagerModuleToolNames.length).toBe(11);
112
+ expect(eagerModuleToolNames.length).toBe(9);
113
113
  });
114
114
 
115
115
  test("explicit tools list includes memory and credential tools", () => {
@@ -187,14 +187,9 @@ describe("baseline characterization: core app tool surface", () => {
187
187
 
188
188
  const nonProxyAppTools = [
189
189
  "app_create",
190
- "app_list",
191
- "app_query",
192
- "app_update",
193
190
  "app_delete",
194
- "app_file_list",
195
- "app_file_read",
196
- "app_file_edit",
197
- "app_file_write",
191
+ "app_generate_icon",
192
+ "app_refresh",
198
193
  ];
199
194
 
200
195
  for (const name of nonProxyAppTools) {
@@ -159,7 +159,7 @@ mock.module("../providers/registry.js", () => {
159
159
  getDefaultModel: (providerName: string) => {
160
160
  const defaults: Record<string, string> = {
161
161
  anthropic: "claude-opus-4-6",
162
- openai: "gpt-5.2",
162
+ openai: "gpt-5.4",
163
163
  gemini: "gemini-3-flash",
164
164
  ollama: "llama3.2",
165
165
  fireworks: "accounts/fireworks/models/kimi-k2p5",
@@ -116,7 +116,7 @@ describe("Runtime attachment metadata", () => {
116
116
  );
117
117
 
118
118
  // Upload and link an attachment using "self" as the assistantId
119
- const stored = uploadAttachment("chart.png", "image/png", "iVBOR");
119
+ const stored = uploadAttachment("chart.png", "image/png", "iVBORw==");
120
120
  linkAttachmentToMessage(assistantMsg.id, stored.id, 0);
121
121
 
122
122
  const res = await fetch(
@@ -181,7 +181,11 @@ describe("Runtime attachment metadata", () => {
181
181
  });
182
182
 
183
183
  test("GET /attachments/:id returns attachment with payload", async () => {
184
- const stored = uploadAttachment("report.pdf", "application/pdf", "JVBER");
184
+ const stored = uploadAttachment(
185
+ "report.pdf",
186
+ "application/pdf",
187
+ "JVBERA==",
188
+ );
185
189
 
186
190
  const res = await fetch(
187
191
  `http://127.0.0.1:${port}/v1/attachments/${stored.id}`,
@@ -201,7 +205,7 @@ describe("Runtime attachment metadata", () => {
201
205
  expect(body.filename).toBe("report.pdf");
202
206
  expect(body.mimeType).toBe("application/pdf");
203
207
  expect(body.kind).toBe("document");
204
- expect(body.data).toBe("JVBER");
208
+ expect(body.data).toBe("JVBERA==");
205
209
  expect(body.sizeBytes).toBeGreaterThan(0);
206
210
  });
207
211