@vellumai/assistant 0.5.0 → 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 (347) 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__/assistant-feature-flags-integration.test.ts +7 -9
  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 +149 -5
  26. package/src/__tests__/conversation-agent-loop.test.ts +290 -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-queue.test.ts +36 -1
  37. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  38. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  39. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  41. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  42. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  43. package/src/__tests__/conversation-store.test.ts +24 -21
  44. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  45. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  46. package/src/__tests__/conversation-title-service.test.ts +137 -0
  47. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  48. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  49. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  50. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  51. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  52. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  53. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  54. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  55. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  56. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  57. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  58. package/src/__tests__/diagnostics-export.test.ts +70 -1
  59. package/src/__tests__/filesystem-tools.test.ts +4 -2
  60. package/src/__tests__/first-greeting.test.ts +80 -0
  61. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  62. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  63. package/src/__tests__/history-repair.test.ts +103 -10
  64. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  65. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  66. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  67. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  68. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  69. package/src/__tests__/media-generate-image.test.ts +47 -94
  70. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  71. package/src/__tests__/memory-recall-quality.test.ts +5 -5
  72. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  73. package/src/__tests__/migration-export-http.test.ts +3 -1
  74. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  75. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  76. package/src/__tests__/mime-builder.test.ts +3 -2
  77. package/src/__tests__/non-member-access-request.test.ts +12 -1
  78. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  80. package/src/__tests__/oauth-store.test.ts +115 -0
  81. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  82. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  83. package/src/__tests__/recording-handler.test.ts +17 -0
  84. package/src/__tests__/registry.test.ts +3 -8
  85. package/src/__tests__/relay-server.test.ts +1 -1
  86. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  87. package/src/__tests__/schema-transforms.test.ts +165 -5
  88. package/src/__tests__/server-history-render.test.ts +2 -2
  89. package/src/__tests__/skill-feature-flags-integration.test.ts +18 -17
  90. package/src/__tests__/skill-feature-flags.test.ts +13 -13
  91. package/src/__tests__/skill-load-feature-flag.test.ts +4 -4
  92. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  93. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  94. package/src/__tests__/starter-task-flow.test.ts +1 -0
  95. package/src/__tests__/suggestion-routes.test.ts +443 -0
  96. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  97. package/src/__tests__/swarm-recursion.test.ts +1 -0
  98. package/src/__tests__/swarm-tool.test.ts +1 -0
  99. package/src/__tests__/system-prompt.test.ts +8 -0
  100. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  101. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  102. package/src/__tests__/top-level-renderer.test.ts +22 -0
  103. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  104. package/src/__tests__/web-fetch.test.ts +6 -2
  105. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  106. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  107. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  108. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  109. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  110. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  111. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  112. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  113. package/src/agent/attachments.ts +27 -1
  114. package/src/agent/loop.ts +29 -1
  115. package/src/avatar/traits-png-sync.ts +80 -25
  116. package/src/bundler/app-bundler.ts +4 -4
  117. package/src/calls/call-domain.ts +1 -0
  118. package/src/calls/voice-session-bridge.ts +1 -0
  119. package/src/cli/commands/auth.ts +92 -0
  120. package/src/cli/commands/avatar.ts +7 -6
  121. package/src/cli/commands/config.ts +2 -0
  122. package/src/cli/commands/oauth/providers.ts +29 -0
  123. package/src/cli/program.ts +12 -0
  124. package/src/cli.ts +15 -48
  125. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  126. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  127. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  128. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  129. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  130. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  131. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  132. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  133. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  134. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  135. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  136. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  137. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  139. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  140. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  141. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  142. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  143. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  144. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  145. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  146. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  148. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  149. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  150. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  151. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  152. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  153. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  154. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  155. package/src/config/bundled-tool-registry.ts +2 -14
  156. package/src/config/feature-flag-registry.json +16 -0
  157. package/src/config/loader.ts +64 -0
  158. package/src/config/raw-config-utils.ts +30 -0
  159. package/src/config/schema-utils.ts +28 -7
  160. package/src/config/schema.ts +8 -0
  161. package/src/config/schemas/elevenlabs.ts +18 -0
  162. package/src/config/schemas/memory-lifecycle.ts +4 -2
  163. package/src/config/schemas/memory-storage.ts +1 -1
  164. package/src/config/schemas/services.ts +8 -6
  165. package/src/contacts/contact-store.ts +13 -6
  166. package/src/contacts/contacts-write.ts +0 -1
  167. package/src/context/window-manager.ts +13 -2
  168. package/src/daemon/conversation-agent-loop-handlers.ts +46 -42
  169. package/src/daemon/conversation-agent-loop.ts +56 -19
  170. package/src/daemon/conversation-attachments.ts +18 -36
  171. package/src/daemon/conversation-error.ts +2 -1
  172. package/src/daemon/conversation-history.ts +18 -4
  173. package/src/daemon/conversation-lifecycle.ts +39 -15
  174. package/src/daemon/conversation-messaging.ts +70 -26
  175. package/src/daemon/conversation-process.ts +58 -34
  176. package/src/daemon/conversation-runtime-assembly.ts +21 -38
  177. package/src/daemon/conversation-slash.ts +121 -256
  178. package/src/daemon/conversation-surfaces.ts +143 -20
  179. package/src/daemon/conversation-tool-setup.ts +0 -6
  180. package/src/daemon/conversation-workspace.ts +21 -1
  181. package/src/daemon/conversation.ts +51 -29
  182. package/src/daemon/first-greeting.ts +35 -0
  183. package/src/daemon/handlers/config-embeddings.ts +148 -0
  184. package/src/daemon/handlers/config-model.ts +71 -26
  185. package/src/daemon/handlers/conversations.ts +0 -23
  186. package/src/daemon/handlers/recording.ts +26 -21
  187. package/src/daemon/history-repair.ts +28 -8
  188. package/src/daemon/host-cu-proxy.ts +2 -2
  189. package/src/daemon/lifecycle.ts +106 -64
  190. package/src/daemon/message-protocol.ts +3 -0
  191. package/src/daemon/message-types/conversations.ts +19 -0
  192. package/src/daemon/message-types/messages.ts +1 -0
  193. package/src/daemon/message-types/shared.ts +2 -0
  194. package/src/daemon/message-types/surfaces.ts +2 -0
  195. package/src/daemon/message-types/upgrades.ts +23 -0
  196. package/src/daemon/server.ts +83 -12
  197. package/src/daemon/shutdown-handlers.ts +8 -5
  198. package/src/daemon/startup-error.ts +9 -0
  199. package/src/daemon/tool-side-effects.ts +11 -28
  200. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  201. package/src/instrument.ts +0 -4
  202. package/src/media/app-icon-generator.ts +2 -2
  203. package/src/memory/app-git-service.ts +28 -16
  204. package/src/memory/app-store.ts +230 -41
  205. package/src/memory/attachments-store.ts +558 -130
  206. package/src/memory/conversation-attention-store.ts +70 -0
  207. package/src/memory/conversation-crud.ts +442 -3
  208. package/src/memory/conversation-directories.ts +125 -0
  209. package/src/memory/conversation-disk-view.ts +390 -0
  210. package/src/memory/conversation-key-store.ts +17 -5
  211. package/src/memory/conversation-queries.ts +5 -1
  212. package/src/memory/conversation-title-service.ts +21 -49
  213. package/src/memory/db-init.ts +28 -0
  214. package/src/memory/embedding-backend.ts +42 -53
  215. package/src/memory/embedding-gemini.test.ts +4 -4
  216. package/src/memory/embedding-local.ts +1 -3
  217. package/src/memory/embedding-ollama.ts +1 -3
  218. package/src/memory/embedding-openai.ts +1 -3
  219. package/src/memory/indexer.ts +9 -7
  220. package/src/memory/items-extractor.ts +42 -13
  221. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  222. package/src/memory/job-handlers/embedding.test.ts +1 -4
  223. package/src/memory/llm-request-log-store.ts +100 -1
  224. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  225. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  226. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  227. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  228. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  229. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  230. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  231. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  232. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  233. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  234. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  235. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  236. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  237. package/src/memory/migrations/index.ts +7 -0
  238. package/src/memory/migrations/registry.ts +13 -0
  239. package/src/memory/retriever.test.ts +601 -2
  240. package/src/memory/retriever.ts +85 -9
  241. package/src/memory/schema/conversations.ts +6 -0
  242. package/src/memory/schema/infrastructure.ts +13 -7
  243. package/src/memory/schema/oauth.ts +6 -0
  244. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  245. package/src/notifications/copy-composer.ts +26 -0
  246. package/src/notifications/decision-engine.ts +14 -1
  247. package/src/notifications/emit-signal.ts +1 -1
  248. package/src/notifications/signal.ts +36 -0
  249. package/src/oauth/byo-connection.test.ts +1 -45
  250. package/src/oauth/byo-connection.ts +2 -8
  251. package/src/oauth/connect-orchestrator.ts +15 -11
  252. package/src/oauth/connection-resolver.test.ts +191 -0
  253. package/src/oauth/connection-resolver.ts +66 -38
  254. package/src/oauth/connection.ts +0 -1
  255. package/src/oauth/oauth-store.ts +97 -47
  256. package/src/oauth/platform-connection.test.ts +0 -1
  257. package/src/oauth/platform-connection.ts +11 -3
  258. package/src/oauth/seed-providers.ts +78 -3
  259. package/src/oauth/token-persistence.ts +16 -10
  260. package/src/permissions/checker.ts +62 -19
  261. package/src/prompts/system-prompt.ts +2 -0
  262. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  263. package/src/providers/anthropic/client.ts +8 -1
  264. package/src/providers/failover.ts +4 -1
  265. package/src/providers/gemini/client.ts +50 -0
  266. package/src/providers/model-catalog.ts +92 -0
  267. package/src/providers/model-intents.ts +29 -20
  268. package/src/providers/openai/client.ts +49 -0
  269. package/src/providers/types.ts +2 -0
  270. package/src/runtime/access-request-helper.ts +16 -7
  271. package/src/runtime/auth/credential-service.ts +3 -1
  272. package/src/runtime/auth/route-policy.ts +14 -1
  273. package/src/runtime/btw-sidechain.ts +101 -0
  274. package/src/runtime/channel-reply-delivery.ts +17 -1
  275. package/src/runtime/http-router.ts +3 -1
  276. package/src/runtime/http-server.ts +196 -141
  277. package/src/runtime/http-types.ts +1 -0
  278. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  279. package/src/runtime/routes/access-request-decision.ts +41 -0
  280. package/src/runtime/routes/app-management-routes.ts +6 -3
  281. package/src/runtime/routes/app-routes.ts +7 -3
  282. package/src/runtime/routes/approval-routes.ts +1 -0
  283. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  284. package/src/runtime/routes/attachment-routes.ts +45 -15
  285. package/src/runtime/routes/btw-routes.ts +21 -61
  286. package/src/runtime/routes/conversation-management-routes.ts +68 -0
  287. package/src/runtime/routes/conversation-query-routes.ts +180 -10
  288. package/src/runtime/routes/conversation-routes.ts +222 -28
  289. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  290. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  291. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  292. package/src/runtime/routes/llm-context-normalization.ts +1199 -0
  293. package/src/runtime/routes/log-export-routes.ts +3 -0
  294. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  295. package/src/runtime/routes/memory-item-routes.ts +4 -0
  296. package/src/runtime/routes/migration-routes.ts +4 -1
  297. package/src/runtime/routes/oauth-apps.ts +291 -0
  298. package/src/runtime/routes/secret-routes.ts +28 -1
  299. package/src/runtime/routes/settings-routes.ts +14 -0
  300. package/src/runtime/routes/trace-event-routes.ts +4 -1
  301. package/src/schedule/schedule-store.ts +9 -21
  302. package/src/security/secure-keys.ts +21 -0
  303. package/src/signals/bash.ts +1 -1
  304. package/src/swarm/backend-claude-code.ts +3 -6
  305. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  306. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  307. package/src/tools/AGENTS.md +6 -10
  308. package/src/tools/apps/executors.ts +17 -232
  309. package/src/tools/claude-code/claude-code.ts +2 -3
  310. package/src/tools/credentials/vault.ts +7 -12
  311. package/src/tools/host-filesystem/read.ts +13 -10
  312. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  313. package/src/tools/schedule/list.ts +2 -7
  314. package/src/tools/schema-transforms.ts +5 -0
  315. package/src/tools/shared/filesystem/format-diff.ts +4 -21
  316. package/src/tools/skills/execute.ts +1 -1
  317. package/src/tools/tool-manifest.ts +0 -6
  318. package/src/tools/ui-surface/definitions.ts +2 -2
  319. package/src/util/device-id.ts +28 -5
  320. package/src/util/platform.ts +6 -0
  321. package/src/util/pricing.ts +1 -0
  322. package/src/util/retry.ts +1 -3
  323. package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
  324. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  325. package/src/workspace/migrations/006-services-config.ts +5 -0
  326. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  327. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  328. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  329. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  330. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  331. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  332. package/src/workspace/migrations/registry.ts +10 -0
  333. package/src/workspace/top-level-renderer.ts +12 -0
  334. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  335. package/src/__tests__/asset-search-tool.test.ts +0 -536
  336. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  337. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  338. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  339. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  340. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  341. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  342. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  343. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  344. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  345. package/src/daemon/media-visibility-policy.ts +0 -59
  346. package/src/tools/assets/materialize.ts +0 -248
  347. package/src/tools/assets/search.ts +0 -400
@@ -0,0 +1,217 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
5
+
6
+ const testDir = mkdtempSync(join(tmpdir(), "llm-context-route-provider-"));
7
+
8
+ mock.module("../util/platform.js", () => ({
9
+ getDataDir: () => testDir,
10
+ isMacOS: () => process.platform === "darwin",
11
+ isLinux: () => process.platform === "linux",
12
+ isWindows: () => process.platform === "win32",
13
+ getPidPath: () => join(testDir, "test.pid"),
14
+ getDbPath: () => join(testDir, "test.db"),
15
+ getLogPath: () => join(testDir, "test.log"),
16
+ ensureDataDir: () => {},
17
+ }));
18
+
19
+ mock.module("../util/logger.js", () => ({
20
+ getLogger: () =>
21
+ new Proxy({} as Record<string, unknown>, {
22
+ get: () => () => {},
23
+ }),
24
+ }));
25
+
26
+ import { getDb, initializeDb, resetDb } from "../memory/db.js";
27
+ import { llmRequestLogs } from "../memory/schema.js";
28
+ import { conversationQueryRouteDefinitions } from "../runtime/routes/conversation-query-routes.js";
29
+
30
+ initializeDb();
31
+
32
+ afterAll(() => {
33
+ resetDb();
34
+ try {
35
+ rmSync(testDir, { recursive: true });
36
+ } catch {
37
+ /* best effort */
38
+ }
39
+ });
40
+
41
+ const routes = conversationQueryRouteDefinitions();
42
+
43
+ function dispatchLlmContext(messageId: string): Promise<Response> | Response {
44
+ const url = new URL(`http://localhost/v1/messages/${messageId}/llm-context`);
45
+ const route = routes.find(
46
+ (r) => r.method === "GET" && r.endpoint === "messages/:id/llm-context",
47
+ );
48
+ if (!route) {
49
+ throw new Error("No llm-context route found");
50
+ }
51
+
52
+ return route.handler({
53
+ req: new Request(url.toString(), { method: "GET" }),
54
+ url,
55
+ server: null as never,
56
+ authContext: {} as never,
57
+ params: { id: messageId },
58
+ });
59
+ }
60
+
61
+ function clearRequestLogs(): void {
62
+ getDb().delete(llmRequestLogs).run();
63
+ }
64
+
65
+ function seedRequestLog(overrides: {
66
+ id: string;
67
+ messageId: string;
68
+ provider: string | null;
69
+ requestPayload: string;
70
+ responsePayload: string;
71
+ createdAt?: number;
72
+ }): void {
73
+ getDb()
74
+ .insert(llmRequestLogs)
75
+ .values({
76
+ id: overrides.id,
77
+ conversationId: "conv-1",
78
+ messageId: overrides.messageId,
79
+ provider: overrides.provider,
80
+ requestPayload: overrides.requestPayload,
81
+ responsePayload: overrides.responsePayload,
82
+ createdAt: overrides.createdAt ?? 1_700_000_000_000,
83
+ })
84
+ .run();
85
+ }
86
+
87
+ beforeEach(() => {
88
+ clearRequestLogs();
89
+ });
90
+
91
+ describe("GET /v1/messages/:id/llm-context provider preference", () => {
92
+ const openAiRequestPayload = JSON.stringify({
93
+ model: "gpt-4.1",
94
+ tool_choice: "auto",
95
+ messages: [
96
+ { role: "system", content: "Stay brief." },
97
+ { role: "user", content: "Hello there." },
98
+ ],
99
+ });
100
+
101
+ const openAiResponsePayload = JSON.stringify({
102
+ model: "gpt-4.1-2026-03-01",
103
+ choices: [
104
+ {
105
+ finish_reason: "stop",
106
+ message: {
107
+ role: "assistant",
108
+ content: "Hello back.",
109
+ },
110
+ },
111
+ ],
112
+ usage: {
113
+ prompt_tokens: 11,
114
+ completion_tokens: 4,
115
+ },
116
+ });
117
+
118
+ test("prefers a stored OpenRouter provider over OpenAI-shaped payload inference", async () => {
119
+ seedRequestLog({
120
+ id: "log-openrouter",
121
+ messageId: "msg-openrouter",
122
+ provider: "openrouter",
123
+ requestPayload: openAiRequestPayload,
124
+ responsePayload: openAiResponsePayload,
125
+ });
126
+
127
+ const response = await dispatchLlmContext("msg-openrouter");
128
+ expect(response.status).toBe(200);
129
+
130
+ const body = (await response.json()) as {
131
+ logs: Array<{
132
+ summary?: { provider: string };
133
+ }>;
134
+ };
135
+
136
+ expect(body.logs).toHaveLength(1);
137
+ expect(body.logs[0]?.summary).toEqual(
138
+ expect.objectContaining({
139
+ provider: "openrouter",
140
+ }),
141
+ );
142
+ });
143
+
144
+ test("prefers a stored Fireworks provider over OpenAI-shaped payload inference", async () => {
145
+ seedRequestLog({
146
+ id: "log-fireworks",
147
+ messageId: "msg-fireworks",
148
+ provider: "fireworks",
149
+ requestPayload: openAiRequestPayload,
150
+ responsePayload: openAiResponsePayload,
151
+ });
152
+
153
+ const response = await dispatchLlmContext("msg-fireworks");
154
+ expect(response.status).toBe(200);
155
+
156
+ const body = (await response.json()) as {
157
+ logs: Array<{
158
+ summary?: { provider: string };
159
+ }>;
160
+ };
161
+
162
+ expect(body.logs).toHaveLength(1);
163
+ expect(body.logs[0]?.summary).toEqual(
164
+ expect.objectContaining({
165
+ provider: "fireworks",
166
+ }),
167
+ );
168
+ });
169
+
170
+ test("keeps the legacy shape-inferred provider when no stored provider exists", async () => {
171
+ seedRequestLog({
172
+ id: "log-legacy",
173
+ messageId: "msg-legacy",
174
+ provider: null,
175
+ requestPayload: openAiRequestPayload,
176
+ responsePayload: openAiResponsePayload,
177
+ });
178
+
179
+ const response = await dispatchLlmContext("msg-legacy");
180
+ expect(response.status).toBe(200);
181
+
182
+ const body = (await response.json()) as {
183
+ logs: Array<{
184
+ summary?: { provider: string };
185
+ }>;
186
+ };
187
+
188
+ expect(body.logs).toHaveLength(1);
189
+ expect(body.logs[0]?.summary).toEqual(
190
+ expect.objectContaining({
191
+ provider: "openai",
192
+ }),
193
+ );
194
+ });
195
+
196
+ test("keeps a stored provider label even when payload normalization fails", async () => {
197
+ seedRequestLog({
198
+ id: "log-raw-only",
199
+ messageId: "msg-raw-only",
200
+ provider: "ollama",
201
+ requestPayload: "not-json",
202
+ responsePayload: "still-not-json",
203
+ });
204
+
205
+ const response = await dispatchLlmContext("msg-raw-only");
206
+ expect(response.status).toBe(200);
207
+
208
+ const body = (await response.json()) as {
209
+ logs: Array<{
210
+ summary?: { provider: string };
211
+ }>;
212
+ };
213
+
214
+ expect(body.logs).toHaveLength(1);
215
+ expect(body.logs[0]?.summary).toEqual({ provider: "ollama" });
216
+ });
217
+ });
@@ -0,0 +1,270 @@
1
+ import { mkdtempSync, realpathSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
5
+
6
+ const testDir = realpathSync(
7
+ mkdtempSync(join(tmpdir(), "llm-request-log-turn-query-test-")),
8
+ );
9
+ const workspaceDir = join(testDir, ".vellum", "workspace");
10
+ const conversationsDir = join(workspaceDir, "conversations");
11
+
12
+ mock.module("../util/platform.js", () => ({
13
+ getRootDir: () => join(testDir, ".vellum"),
14
+ getDataDir: () => join(workspaceDir, "data"),
15
+ getWorkspaceDir: () => workspaceDir,
16
+ getConversationsDir: () => conversationsDir,
17
+ isMacOS: () => process.platform === "darwin",
18
+ isLinux: () => process.platform === "linux",
19
+ isWindows: () => process.platform === "win32",
20
+ getPidPath: () => join(testDir, "test.pid"),
21
+ getDbPath: () => join(testDir, "test.db"),
22
+ getLogPath: () => join(testDir, "test.log"),
23
+ ensureDataDir: () => {},
24
+ }));
25
+
26
+ mock.module("../util/logger.js", () => ({
27
+ getLogger: () =>
28
+ new Proxy({} as Record<string, unknown>, {
29
+ get: () => () => {},
30
+ }),
31
+ }));
32
+
33
+ mock.module("../config/loader.js", () => ({
34
+ getConfig: () => ({
35
+ ui: {},
36
+ model: "test",
37
+ provider: "test",
38
+ memory: { enabled: false },
39
+ rateLimit: { maxRequestsPerMinute: 0 },
40
+ secretDetection: { enabled: false },
41
+ }),
42
+ }));
43
+
44
+ import {
45
+ addMessage,
46
+ createConversation,
47
+ forkConversation,
48
+ } from "../memory/conversation-crud.js";
49
+ import { getDb, initializeDb, resetDb } from "../memory/db.js";
50
+ import {
51
+ backfillMessageIdOnLogs,
52
+ getRequestLogsByMessageId,
53
+ recordRequestLog,
54
+ } from "../memory/llm-request-log-store.js";
55
+ import { llmRequestLogs, toolInvocations } from "../memory/schema.js";
56
+
57
+ initializeDb();
58
+
59
+ function resetTables(): void {
60
+ const db = getDb();
61
+ db.delete(llmRequestLogs).run();
62
+ db.delete(toolInvocations).run();
63
+ db.run("DELETE FROM message_attachments");
64
+ db.run("DELETE FROM attachments");
65
+ db.run("DELETE FROM messages");
66
+ db.run("DELETE FROM conversations");
67
+ }
68
+
69
+ function toolResultContent(toolUseIds: string[]): string {
70
+ return JSON.stringify(
71
+ toolUseIds.map((id) => ({
72
+ type: "tool_result",
73
+ tool_use_id: id,
74
+ content: "ok",
75
+ is_error: false,
76
+ })),
77
+ );
78
+ }
79
+
80
+ afterAll(() => {
81
+ resetDb();
82
+ try {
83
+ rmSync(testDir, { recursive: true, force: true });
84
+ } catch {
85
+ /* best effort */
86
+ }
87
+ });
88
+
89
+ describe("getRequestLogsByMessageId — turn-aware query", () => {
90
+ beforeEach(() => {
91
+ resetTables();
92
+ });
93
+
94
+ test("single message, single log: backward compat — returns 1 log", async () => {
95
+ const conv = createConversation("single-msg");
96
+ await addMessage(conv.id, "user", "Hello", undefined, {
97
+ skipIndexing: true,
98
+ });
99
+ const a1 = await addMessage(conv.id, "assistant", "Hi!", undefined, {
100
+ skipIndexing: true,
101
+ });
102
+
103
+ // Record a log without messageId, then backfill
104
+ recordRequestLog(conv.id, '{"prompt":"hi"}', '{"result":"hello"}');
105
+ backfillMessageIdOnLogs(conv.id, a1.id);
106
+
107
+ const logs = getRequestLogsByMessageId(a1.id);
108
+ expect(logs).toHaveLength(1);
109
+ expect(logs[0]?.messageId).toBe(a1.id);
110
+ expect(logs[0]?.conversationId).toBe(conv.id);
111
+ });
112
+
113
+ test("multi-step turn: returns logs from all assistant messages in the turn", async () => {
114
+ const conv = createConversation("multi-step");
115
+
116
+ // user → A1 (+ log1) → tool_result → A2 (+ log2)
117
+ await addMessage(conv.id, "user", "Do the task", undefined, {
118
+ skipIndexing: true,
119
+ });
120
+
121
+ // First LLM call → A1
122
+ recordRequestLog(conv.id, '{"step":1}', '{"tool_use":"bash"}');
123
+ const a1 = await addMessage(
124
+ conv.id,
125
+ "assistant",
126
+ "Using tool...",
127
+ undefined,
128
+ { skipIndexing: true },
129
+ );
130
+ backfillMessageIdOnLogs(conv.id, a1.id);
131
+
132
+ // tool_result user message
133
+ await addMessage(
134
+ conv.id,
135
+ "user",
136
+ toolResultContent(["tool-1"]),
137
+ undefined,
138
+ { skipIndexing: true },
139
+ );
140
+
141
+ // Second LLM call → A2
142
+ recordRequestLog(conv.id, '{"step":2}', '{"result":"done"}');
143
+ const a2 = await addMessage(conv.id, "assistant", "All done!", undefined, {
144
+ skipIndexing: true,
145
+ });
146
+ backfillMessageIdOnLogs(conv.id, a2.id);
147
+
148
+ // Query from A2 (the last message in the turn) → should return both logs
149
+ const logs = getRequestLogsByMessageId(a2.id);
150
+ expect(logs).toHaveLength(2);
151
+ expect(logs[0]?.messageId).toBe(a1.id);
152
+ expect(logs[1]?.messageId).toBe(a2.id);
153
+ // Verify ordering is by createdAt ASC
154
+ expect(logs[0]!.createdAt).toBeLessThanOrEqual(logs[1]!.createdAt);
155
+ });
156
+
157
+ test("fork fallback still works: forked message with no logs, source has turn logs", async () => {
158
+ const source = createConversation("source-conv");
159
+
160
+ // Build a multi-step turn in the source conversation
161
+ await addMessage(source.id, "user", "Original task", undefined, {
162
+ skipIndexing: true,
163
+ });
164
+
165
+ recordRequestLog(source.id, '{"step":1}', '{"tool":"bash"}');
166
+ const a1 = await addMessage(
167
+ source.id,
168
+ "assistant",
169
+ "Using tool...",
170
+ undefined,
171
+ { skipIndexing: true },
172
+ );
173
+ backfillMessageIdOnLogs(source.id, a1.id);
174
+
175
+ await addMessage(
176
+ source.id,
177
+ "user",
178
+ toolResultContent(["tool-1"]),
179
+ undefined,
180
+ { skipIndexing: true },
181
+ );
182
+
183
+ recordRequestLog(source.id, '{"step":2}', '{"result":"ok"}');
184
+ const a2 = await addMessage(
185
+ source.id,
186
+ "assistant",
187
+ "Done with source!",
188
+ undefined,
189
+ { skipIndexing: true },
190
+ );
191
+ backfillMessageIdOnLogs(source.id, a2.id);
192
+
193
+ // Fork the conversation
194
+ const fork = forkConversation({ conversationId: source.id });
195
+ const forkMessages = (
196
+ await import("../memory/conversation-crud.js")
197
+ ).getMessages(fork.id);
198
+ const forkLastAssistant = forkMessages
199
+ .filter((m) => m.role === "assistant")
200
+ .at(-1);
201
+ expect(forkLastAssistant).toBeDefined();
202
+
203
+ // The fork has no LLM logs of its own — should fall back to source turn's logs
204
+ const logs = getRequestLogsByMessageId(forkLastAssistant!.id);
205
+ expect(logs).toHaveLength(2);
206
+ expect(logs[0]?.conversationId).toBe(source.id);
207
+ expect(logs[1]?.conversationId).toBe(source.id);
208
+ });
209
+
210
+ test("logs from different turns don't bleed", async () => {
211
+ const conv = createConversation("two-turns");
212
+
213
+ // First turn: user → A1 (+ log1)
214
+ await addMessage(conv.id, "user", "First question", undefined, {
215
+ skipIndexing: true,
216
+ });
217
+ recordRequestLog(conv.id, '{"turn":1}', '{"answer":"first"}');
218
+ const a1 = await addMessage(
219
+ conv.id,
220
+ "assistant",
221
+ "First answer",
222
+ undefined,
223
+ { skipIndexing: true },
224
+ );
225
+ backfillMessageIdOnLogs(conv.id, a1.id);
226
+
227
+ // Second turn: user → A2 (+ log2) → tool_result → A3 (+ log3)
228
+ await addMessage(conv.id, "user", "Second question", undefined, {
229
+ skipIndexing: true,
230
+ });
231
+ recordRequestLog(conv.id, '{"turn":2,"step":1}', '{"tool":"bash"}');
232
+ const a2 = await addMessage(
233
+ conv.id,
234
+ "assistant",
235
+ "Using tool...",
236
+ undefined,
237
+ { skipIndexing: true },
238
+ );
239
+ backfillMessageIdOnLogs(conv.id, a2.id);
240
+
241
+ await addMessage(
242
+ conv.id,
243
+ "user",
244
+ toolResultContent(["tool-2"]),
245
+ undefined,
246
+ { skipIndexing: true },
247
+ );
248
+
249
+ recordRequestLog(conv.id, '{"turn":2,"step":2}', '{"result":"done"}');
250
+ const a3 = await addMessage(
251
+ conv.id,
252
+ "assistant",
253
+ "Second done!",
254
+ undefined,
255
+ { skipIndexing: true },
256
+ );
257
+ backfillMessageIdOnLogs(conv.id, a3.id);
258
+
259
+ // Query second turn → should only return logs for A2 and A3, NOT A1
260
+ const secondTurnLogs = getRequestLogsByMessageId(a3.id);
261
+ expect(secondTurnLogs).toHaveLength(2);
262
+ expect(secondTurnLogs[0]?.messageId).toBe(a2.id);
263
+ expect(secondTurnLogs[1]?.messageId).toBe(a3.id);
264
+
265
+ // Query first turn → should only return log for A1
266
+ const firstTurnLogs = getRequestLogsByMessageId(a1.id);
267
+ expect(firstTurnLogs).toHaveLength(1);
268
+ expect(firstTurnLogs[0]?.messageId).toBe(a1.id);
269
+ });
270
+ });
@@ -77,84 +77,6 @@ mock.module("../providers/managed-proxy/context.js", () => ({
77
77
  resolveManagedProxyContext: async () => mockManagedProxyContext,
78
78
  }));
79
79
 
80
- let mockAttachments: Array<{
81
- id: string;
82
- assistantId: string;
83
- originalFilename: string;
84
- mimeType: string;
85
- sizeBytes: number;
86
- kind: string;
87
- createdAt: number;
88
- dataBase64: string;
89
- }> = [];
90
-
91
- mock.module("../memory/attachments-store.js", () => ({
92
- getAttachmentsByIds: (_assistantId: string, _ids: string[]) =>
93
- mockAttachments,
94
- getAttachmentContent: (id: string) => {
95
- const att = mockAttachments.find((a) => a.id === id);
96
- if (!att) return null;
97
- return Buffer.from(att.dataBase64, "base64");
98
- },
99
- }));
100
-
101
- mock.module("../memory/db.js", () => ({
102
- getDb: () => ({
103
- select: () => ({
104
- from: () => ({
105
- where: () => ({
106
- get: () => ({ assistantId: "test-assistant" }),
107
- }),
108
- }),
109
- }),
110
- }),
111
- }));
112
-
113
- mock.module("../memory/schema.js", () => ({
114
- conversationKeys: {
115
- assistantId: "assistantId",
116
- conversationId: "conversationId",
117
- },
118
- }));
119
-
120
- mock.module("drizzle-orm", () => ({
121
- eq: () => true,
122
- }));
123
-
124
- mock.module("../memory/conversation-crud.js", () => ({
125
- setConversationOriginChannelIfUnset: () => {},
126
- updateConversationContextWindow: () => {},
127
- deleteMessageById: () => {},
128
- updateConversationTitle: () => {},
129
- updateConversationUsage: () => {},
130
- addMessage: () => ({ id: "mock-msg-id" }),
131
- getMessages: () => [],
132
- getConversation: () => ({
133
- id: "conv-1",
134
- contextSummary: null,
135
- contextCompactedMessageCount: 0,
136
- totalInputTokens: 0,
137
- totalOutputTokens: 0,
138
- totalEstimatedCost: 0,
139
- title: null,
140
- }),
141
- provenanceFromTrustContext: () => ({
142
- source: "user",
143
- trustContext: undefined,
144
- }),
145
- getConversationOriginInterface: () => null,
146
- getConversationOriginChannel: () => null,
147
- getConversationType: () => "standard",
148
- }));
149
-
150
- mock.module("../daemon/media-visibility-policy.js", () => ({
151
- isAttachmentVisible: () => true,
152
- }));
153
-
154
- mock.module("../tools/assets/search.js", () => ({
155
- getAttachmentSourceConversations: () => [],
156
- }));
157
-
158
80
  // Import after mocking
159
81
  import { run } from "../config/bundled-skills/image-studio/tools/media-generate-image.js";
160
82
 
@@ -183,7 +105,6 @@ beforeEach(() => {
183
105
  resolvedModel: "gemini-3.1-flash-image-preview",
184
106
  };
185
107
  mockGenerateError = null;
186
- mockAttachments = [];
187
108
  lastGenerateCredentials = null;
188
109
  mockManagedBaseUrl = undefined;
189
110
  mockManagedProxyContext = {
@@ -307,26 +228,57 @@ describe("image-studio skill script wrapper", () => {
307
228
  expect(result.content).toContain("Mock error: API failure");
308
229
  });
309
230
 
310
- test("passes attachment data as sourceImages for edit mode", async () => {
311
- mockAttachments = [
231
+ test("reads source images from file paths on disk", async () => {
232
+ // Write a temp image file inside the workspace (fakeContext.workingDir = /tmp)
233
+ const tmpPath = join("/tmp", "test-source-image.png");
234
+ const pngBytes = Buffer.from(
235
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
236
+ "base64",
237
+ );
238
+ await Bun.write(tmpPath, pngBytes);
239
+
240
+ try {
241
+ const result = await run(
242
+ { prompt: "edit this", mode: "edit", source_paths: [tmpPath] },
243
+ fakeContext,
244
+ );
245
+
246
+ expect(result.isError).toBe(false);
247
+ expect(result.content).toContain("Generated 1 image");
248
+ } finally {
249
+ const { unlink } = await import("fs/promises");
250
+ if (await Bun.file(tmpPath).exists()) await unlink(tmpPath);
251
+ }
252
+ });
253
+
254
+ test("returns error when all source_paths are invalid", async () => {
255
+ const result = await run(
312
256
  {
313
- id: "att-1",
314
- assistantId: "test-assistant",
315
- originalFilename: "photo.jpg",
316
- mimeType: "image/jpeg",
317
- sizeBytes: 1000,
318
- kind: "image",
319
- createdAt: Date.now(),
320
- dataBase64: "attachment-data",
257
+ prompt: "edit this",
258
+ mode: "edit",
259
+ source_paths: ["/nonexistent/path.png"],
321
260
  },
322
- ];
261
+ fakeContext,
262
+ );
323
263
 
264
+ expect(result.isError).toBe(true);
265
+ expect(result.content).toContain(
266
+ "None of the specified file paths could be read",
267
+ );
268
+ });
269
+
270
+ test("rejects source_paths outside the workspace", async () => {
324
271
  const result = await run(
325
- { prompt: "remove bg", mode: "edit", attachment_ids: ["att-1"] },
272
+ {
273
+ prompt: "edit this",
274
+ mode: "edit",
275
+ source_paths: ["/etc/passwd"],
276
+ },
326
277
  fakeContext,
327
278
  );
328
279
 
329
- expect(result.isError).toBe(false);
280
+ expect(result.isError).toBe(true);
281
+ expect(result.content).toContain("outside the working directory");
330
282
  });
331
283
  });
332
284
 
@@ -370,10 +322,11 @@ describe("image-studio TOOLS.json manifest", () => {
370
322
  expect(schema.properties.prompt.type).toBe("string");
371
323
  });
372
324
 
373
- test("input schema has optional mode, attachment_ids, model, variants", () => {
325
+ test("input schema has optional mode, source_paths, model, variants", () => {
374
326
  const props = manifest.tools[0].input_schema.properties;
375
327
  expect(props.mode.enum).toEqual(["generate", "edit"]);
376
- expect(props.attachment_ids.type).toBe("array");
328
+ expect(props.source_paths.type).toBe("array");
329
+ expect(props.attachment_ids).toBeUndefined();
377
330
  expect(props.model.enum).toEqual([
378
331
  "gemini-3.1-flash-image-preview",
379
332
  "gemini-3-pro-image-preview",
@@ -394,7 +394,9 @@ describe("Memory lifecycle E2E regression", () => {
394
394
  expect(b1.type === "text" && b1.text).toBe("Actual user request");
395
395
 
396
396
  // Stripped by prefix-based stripping (same mechanism as workspace/temporal)
397
- const cleaned = stripUserTextBlocksByPrefix(injected, ["<memory_context __injected>"]);
397
+ const cleaned = stripUserTextBlocksByPrefix(injected, [
398
+ "<memory_context __injected>",
399
+ ]);
398
400
  expect(cleaned).toHaveLength(1);
399
401
  expect(cleaned[0].content).toHaveLength(1);
400
402
  const cb0 = cleaned[0].content[0];