@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
@@ -491,12 +491,17 @@ describe("repairHistory", () => {
491
491
  expect(assistantMsg.content[1]).toMatchObject({
492
492
  type: "web_search_tool_result",
493
493
  tool_use_id: "stu_1",
494
- content: { type: "web_search_tool_result_error", error_code: "unavailable" },
494
+ content: {
495
+ type: "web_search_tool_result_error",
496
+ error_code: "unavailable",
497
+ },
495
498
  });
496
499
 
497
500
  // User message has no web_search_tool_result
498
501
  const userMsg = repaired[2];
499
- expect(userMsg.content.every((b) => b.type !== "web_search_tool_result")).toBe(true);
502
+ expect(
503
+ userMsg.content.every((b) => b.type !== "web_search_tool_result"),
504
+ ).toBe(true);
500
505
  });
501
506
 
502
507
  test("migrates legacy web_search_tool_result from user message to assistant message", () => {
@@ -527,7 +532,9 @@ describe("repairHistory", () => {
527
532
  {
528
533
  type: "web_search_tool_result",
529
534
  tool_use_id: "srvtoolu_abc",
530
- content: [{ type: "web_search_result", url: "https://example.com" }],
535
+ content: [
536
+ { type: "web_search_result", url: "https://example.com" },
537
+ ],
531
538
  },
532
539
  { type: "tool_result", tool_use_id: "tu_1", content: "files" },
533
540
  ],
@@ -545,8 +552,12 @@ describe("repairHistory", () => {
545
552
 
546
553
  // The assistant message now has the server pair + client tool_use
547
554
  const assistantMsg = repaired[1];
548
- const serverToolUse = assistantMsg.content.find((b) => b.type === "server_tool_use");
549
- const webSearchResult = assistantMsg.content.find((b) => b.type === "web_search_tool_result");
555
+ const serverToolUse = assistantMsg.content.find(
556
+ (b) => b.type === "server_tool_use",
557
+ );
558
+ const webSearchResult = assistantMsg.content.find(
559
+ (b) => b.type === "web_search_tool_result",
560
+ );
550
561
  expect(serverToolUse).toBeDefined();
551
562
  expect(webSearchResult).toBeDefined();
552
563
 
@@ -554,7 +565,9 @@ describe("repairHistory", () => {
554
565
  const userMsg = repaired[2];
555
566
  expect(stats.orphanToolResultsDowngraded).toBe(1);
556
567
  expect(userMsg.content.some((b) => b.type === "tool_result")).toBe(true);
557
- expect(userMsg.content.every((b) => b.type !== "web_search_tool_result")).toBe(true);
568
+ expect(
569
+ userMsg.content.every((b) => b.type !== "web_search_tool_result"),
570
+ ).toBe(true);
558
571
  });
559
572
 
560
573
  test("trailing server_tool_use gets synthetic result in same assistant message", () => {
@@ -584,10 +597,84 @@ describe("repairHistory", () => {
584
597
  expect(repaired[1].content[1]).toMatchObject({
585
598
  type: "web_search_tool_result",
586
599
  tool_use_id: "stu_1",
587
- content: { type: "web_search_tool_result_error", error_code: "unavailable" },
600
+ content: {
601
+ type: "web_search_tool_result_error",
602
+ error_code: "unavailable",
603
+ },
588
604
  });
589
605
  });
590
606
 
607
+ test("synthetic web_search_tool_result is placed immediately after its server_tool_use, not at end", () => {
608
+ // Regression: synthetic results appended to the end of the content array
609
+ // get separated from their server_tool_use by ensureToolPairing's split
610
+ // at tool_use boundaries, causing the API to reject with "web_search
611
+ // tool use without a corresponding web_search_tool_result block".
612
+ const messages: Message[] = [
613
+ { role: "user", content: [{ type: "text", text: "Search and act" }] },
614
+ {
615
+ role: "assistant",
616
+ content: [
617
+ { type: "text", text: "Let me search" },
618
+ {
619
+ type: "server_tool_use",
620
+ id: "stu_1",
621
+ name: "web_search",
622
+ input: { query: "openai" },
623
+ },
624
+ {
625
+ type: "server_tool_use",
626
+ id: "stu_2",
627
+ name: "web_search",
628
+ input: { query: "anthropic" },
629
+ },
630
+ { type: "text", text: "Based on my research" },
631
+ {
632
+ type: "tool_use",
633
+ id: "tu_1",
634
+ name: "skill_load",
635
+ input: { skill: "app-builder" },
636
+ },
637
+ ],
638
+ },
639
+ {
640
+ role: "user",
641
+ content: [
642
+ {
643
+ type: "tool_result",
644
+ tool_use_id: "tu_1",
645
+ content: "Skill loaded",
646
+ },
647
+ ],
648
+ },
649
+ ];
650
+
651
+ const { messages: repaired, stats } = repairHistory(messages);
652
+
653
+ expect(stats.missingToolResultsInserted).toBe(2);
654
+
655
+ const assistantMsg = repaired[1];
656
+ // Synthetic results must appear immediately after their server_tool_use,
657
+ // NOT after the tool_use block at the end
658
+ const blockTypes = assistantMsg.content.map((b) => b.type);
659
+ expect(blockTypes).toEqual([
660
+ "text",
661
+ "server_tool_use",
662
+ "web_search_tool_result", // right after stu_1
663
+ "server_tool_use",
664
+ "web_search_tool_result", // right after stu_2
665
+ "text",
666
+ "tool_use",
667
+ ]);
668
+
669
+ // Verify the pairings are correct
670
+ expect(
671
+ (assistantMsg.content[2] as { tool_use_id: string }).tool_use_id,
672
+ ).toBe("stu_1");
673
+ expect(
674
+ (assistantMsg.content[4] as { tool_use_id: string }).tool_use_id,
675
+ ).toBe("stu_2");
676
+ });
677
+
591
678
  test("downgrades type-mismatched tool_result for server_tool_use", () => {
592
679
  // A tool_result in the user message for a server_tool_use ID is orphaned —
593
680
  // server-side results belong in the assistant message
@@ -625,11 +712,15 @@ describe("repairHistory", () => {
625
712
 
626
713
  // Assistant message has the server pair
627
714
  const assistantMsg = repaired[1];
628
- expect(assistantMsg.content.some((b) => b.type === "web_search_tool_result")).toBe(true);
715
+ expect(
716
+ assistantMsg.content.some((b) => b.type === "web_search_tool_result"),
717
+ ).toBe(true);
629
718
 
630
719
  // User message has no web_search_tool_result — the tool_result was downgraded to text
631
720
  const userMsg = repaired[2];
632
- expect(userMsg.content.every((b) => b.type !== "web_search_tool_result")).toBe(true);
721
+ expect(
722
+ userMsg.content.every((b) => b.type !== "web_search_tool_result"),
723
+ ).toBe(true);
633
724
  expect(userMsg.content.every((b) => b.type !== "tool_result")).toBe(true);
634
725
  });
635
726
 
@@ -649,7 +740,9 @@ describe("repairHistory", () => {
649
740
  {
650
741
  type: "web_search_tool_result",
651
742
  tool_use_id: "tu_1",
652
- content: [{ type: "web_search_result", url: "https://example.com" }],
743
+ content: [
744
+ { type: "web_search_result", url: "https://example.com" },
745
+ ],
653
746
  },
654
747
  ],
655
748
  },
@@ -0,0 +1,251 @@
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(), "http-conversation-lineage-test-")),
8
+ );
9
+
10
+ mock.module("../util/platform.js", () => ({
11
+ getRootDir: () => join(testDir, ".vellum"),
12
+ getDataDir: () => join(testDir, ".vellum", "workspace", "data"),
13
+ getWorkspaceDir: () => join(testDir, ".vellum", "workspace"),
14
+ getConversationsDir: () =>
15
+ join(testDir, ".vellum", "workspace", "conversations"),
16
+ isMacOS: () => process.platform === "darwin",
17
+ isLinux: () => process.platform === "linux",
18
+ isWindows: () => process.platform === "win32",
19
+ getPidPath: () => join(testDir, "test.pid"),
20
+ getDbPath: () => join(testDir, "test.db"),
21
+ getLogPath: () => join(testDir, "test.log"),
22
+ ensureDataDir: () => {},
23
+ }));
24
+
25
+ mock.module("../util/logger.js", () => ({
26
+ getLogger: () =>
27
+ new Proxy({} as Record<string, unknown>, {
28
+ get: () => () => {},
29
+ }),
30
+ }));
31
+
32
+ mock.module("../config/env.js", () => ({
33
+ isHttpAuthDisabled: () => true,
34
+ hasUngatedHttpAuthDisabled: () => false,
35
+ getGatewayInternalBaseUrl: () => "http://127.0.0.1:7830",
36
+ getGatewayPort: () => 7830,
37
+ getRuntimeHttpPort: () => 7821,
38
+ getRuntimeHttpHost: () => "127.0.0.1",
39
+ getRuntimeGatewayOriginSecret: () => undefined,
40
+ getIngressPublicBaseUrl: () => undefined,
41
+ setIngressPublicBaseUrl: () => {},
42
+ }));
43
+
44
+ mock.module("../config/loader.js", () => ({
45
+ getConfig: () => ({
46
+ ui: {},
47
+ model: "test",
48
+ provider: "test",
49
+ memory: { enabled: false },
50
+ rateLimit: { maxRequestsPerMinute: 0 },
51
+ secretDetection: { enabled: false },
52
+ }),
53
+ }));
54
+
55
+ import {
56
+ batchSetDisplayOrders,
57
+ createConversation,
58
+ updateConversationTitle,
59
+ } from "../memory/conversation-crud.js";
60
+ import { getDb, initializeDb, rawRun, resetDb } from "../memory/db.js";
61
+ import { RuntimeHttpServer } from "../runtime/http-server.js";
62
+
63
+ initializeDb();
64
+
65
+ type ConversationSummary = {
66
+ id: string;
67
+ title: string;
68
+ displayOrder?: number | null;
69
+ isPinned?: boolean;
70
+ forkParent?: {
71
+ conversationId: string;
72
+ messageId: string;
73
+ title: string;
74
+ };
75
+ };
76
+
77
+ describe("conversation lineage in HTTP reads", () => {
78
+ let server: RuntimeHttpServer | null = null;
79
+
80
+ beforeEach(async () => {
81
+ await server?.stop();
82
+ server = null;
83
+ clearTables();
84
+ });
85
+
86
+ afterAll(async () => {
87
+ await server?.stop();
88
+ resetDb();
89
+ try {
90
+ rmSync(testDir, { recursive: true, force: true });
91
+ } catch {
92
+ /* best effort */
93
+ }
94
+ });
95
+
96
+ test("GET /v1/conversations returns forkParent for surviving parents", async () => {
97
+ const { child, parent } = seedForkedConversation();
98
+ await startServer();
99
+
100
+ const response = await fetch(url("/conversations"));
101
+ expect(response.status).toBe(200);
102
+
103
+ const body = (await response.json()) as {
104
+ conversations: ConversationSummary[];
105
+ hasMore: boolean;
106
+ };
107
+ const listedChild = body.conversations.find((item) => item.id === child.id);
108
+
109
+ expect(listedChild).toMatchObject({
110
+ id: child.id,
111
+ title: child.title ?? "Untitled",
112
+ forkParent: {
113
+ conversationId: parent.id,
114
+ messageId: "parent-msg-1",
115
+ title: parent.title ?? "Untitled",
116
+ },
117
+ });
118
+ expect(body.hasMore).toBe(false);
119
+ });
120
+
121
+ test("GET /v1/conversations/:id returns forkParent for surviving parents", async () => {
122
+ const { child, parent } = seedForkedConversation();
123
+ await startServer();
124
+
125
+ const response = await fetch(url(`/conversations/${child.id}`));
126
+ expect(response.status).toBe(200);
127
+
128
+ const body = (await response.json()) as {
129
+ conversation: ConversationSummary;
130
+ };
131
+
132
+ expect(body.conversation).toMatchObject({
133
+ id: child.id,
134
+ title: child.title ?? "Untitled",
135
+ forkParent: {
136
+ conversationId: parent.id,
137
+ messageId: "parent-msg-1",
138
+ title: parent.title ?? "Untitled",
139
+ },
140
+ });
141
+ });
142
+
143
+ test("GET /v1/conversations/:id includes pin metadata when present", async () => {
144
+ const conversation = createConversation("Pinned conversation");
145
+ batchSetDisplayOrders([
146
+ { id: conversation.id, displayOrder: 7, isPinned: true },
147
+ ]);
148
+ await startServer();
149
+
150
+ const response = await fetch(url(`/conversations/${conversation.id}`));
151
+ expect(response.status).toBe(200);
152
+
153
+ const body = (await response.json()) as {
154
+ conversation: ConversationSummary;
155
+ };
156
+
157
+ expect(body.conversation).toMatchObject({
158
+ id: conversation.id,
159
+ title: conversation.title ?? "Untitled",
160
+ displayOrder: 7,
161
+ isPinned: true,
162
+ });
163
+ });
164
+
165
+ test("GET /v1/conversations/:id resolves the parent's current title at read time", async () => {
166
+ const { child, parent } = seedForkedConversation({
167
+ parentTitle: "Original parent title",
168
+ });
169
+ updateConversationTitle(parent.id, "Renamed parent title", 0);
170
+ await startServer();
171
+
172
+ const response = await fetch(url(`/conversations/${child.id}`));
173
+ expect(response.status).toBe(200);
174
+
175
+ const body = (await response.json()) as {
176
+ conversation: ConversationSummary;
177
+ };
178
+
179
+ expect(body.conversation.forkParent).toEqual({
180
+ conversationId: parent.id,
181
+ messageId: "parent-msg-1",
182
+ title: "Renamed parent title",
183
+ });
184
+ });
185
+
186
+ test("deleted parents are omitted from list and detail responses", async () => {
187
+ const { child, parent } = seedForkedConversation();
188
+ rawRun("DELETE FROM conversations WHERE id = ?", parent.id);
189
+ await startServer();
190
+
191
+ const listResponse = await fetch(url("/conversations"));
192
+ expect(listResponse.status).toBe(200);
193
+ const listBody = (await listResponse.json()) as {
194
+ conversations: ConversationSummary[];
195
+ };
196
+ const listedChild = listBody.conversations.find(
197
+ (item) => item.id === child.id,
198
+ );
199
+ expect(listedChild).toBeDefined();
200
+ expect(listedChild?.forkParent).toBeUndefined();
201
+
202
+ const detailResponse = await fetch(url(`/conversations/${child.id}`));
203
+ expect(detailResponse.status).toBe(200);
204
+ const detailBody = (await detailResponse.json()) as {
205
+ conversation: ConversationSummary;
206
+ };
207
+ expect(detailBody.conversation.forkParent).toBeUndefined();
208
+ });
209
+
210
+ function clearTables(): void {
211
+ const db = getDb();
212
+ db.run("DELETE FROM conversation_assistant_attention_state");
213
+ db.run("DELETE FROM external_conversation_bindings");
214
+ db.run("DELETE FROM conversation_keys");
215
+ db.run("DELETE FROM messages");
216
+ db.run("DELETE FROM conversations");
217
+ }
218
+
219
+ function seedForkedConversation(opts?: { parentTitle?: string }) {
220
+ const parent = createConversation(
221
+ opts?.parentTitle ?? "Parent conversation",
222
+ );
223
+ const child = createConversation("Forked conversation");
224
+
225
+ rawRun(
226
+ `
227
+ UPDATE conversations
228
+ SET fork_parent_conversation_id = ?, fork_parent_message_id = ?
229
+ WHERE id = ?
230
+ `,
231
+ parent.id,
232
+ "parent-msg-1",
233
+ child.id,
234
+ );
235
+
236
+ return { parent, child };
237
+ }
238
+
239
+ async function startServer(): Promise<void> {
240
+ server = new RuntimeHttpServer({
241
+ port: 0,
242
+ bearerToken: "test-bearer-token",
243
+ });
244
+ await server.start();
245
+ }
246
+
247
+ function url(pathname: string): string {
248
+ if (!server) throw new Error("server not started");
249
+ return `http://127.0.0.1:${server.actualPort}/v1${pathname}`;
250
+ }
251
+ });
@@ -0,0 +1,136 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { reinjectImageSourcePaths } from "../daemon/conversation-lifecycle.js";
4
+ import type { ContentBlock } from "../providers/types.js";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // reinjectImageSourcePaths — re-inject [Attached image source: /path]
8
+ // annotations when loading conversation history from DB
9
+ // ---------------------------------------------------------------------------
10
+
11
+ describe("reinjectImageSourcePaths", () => {
12
+ const baseContent: ContentBlock[] = [
13
+ { type: "text", text: "what is this?" },
14
+ {
15
+ type: "image",
16
+ source: { type: "base64", media_type: "image/jpeg", data: "base64img" },
17
+ },
18
+ ];
19
+
20
+ test("adds annotation when user message has imageSourcePaths in metadata", () => {
21
+ const metadata = JSON.stringify({
22
+ imageSourcePaths: { "photo.jpg": "/Users/me/Desktop/photo.jpg" },
23
+ });
24
+ const result = reinjectImageSourcePaths(baseContent, "user", metadata);
25
+
26
+ expect(result).toHaveLength(3);
27
+ const annotation = result[2] as { type: "text"; text: string };
28
+ expect(annotation.type).toBe("text");
29
+ expect(annotation.text).toBe(
30
+ "[Attached image source: /Users/me/Desktop/photo.jpg]",
31
+ );
32
+ });
33
+
34
+ test("does NOT annotate assistant messages even if metadata has imageSourcePaths", () => {
35
+ const metadata = JSON.stringify({
36
+ imageSourcePaths: { "photo.jpg": "/Users/me/Desktop/photo.jpg" },
37
+ });
38
+ const result = reinjectImageSourcePaths(baseContent, "assistant", metadata);
39
+
40
+ // Should return the original content unchanged
41
+ expect(result).toBe(baseContent);
42
+ expect(result).toHaveLength(2);
43
+ });
44
+
45
+ test("returns content unchanged when metadata is null", () => {
46
+ const result = reinjectImageSourcePaths(baseContent, "user", null);
47
+ expect(result).toBe(baseContent);
48
+ expect(result).toHaveLength(2);
49
+ });
50
+
51
+ test("returns content unchanged when metadata has no imageSourcePaths", () => {
52
+ const metadata = JSON.stringify({
53
+ userMessageChannel: "desktop",
54
+ });
55
+ const result = reinjectImageSourcePaths(baseContent, "user", metadata);
56
+ expect(result).toBe(baseContent);
57
+ expect(result).toHaveLength(2);
58
+ });
59
+
60
+ test("returns content unchanged when imageSourcePaths is empty object", () => {
61
+ const metadata = JSON.stringify({
62
+ imageSourcePaths: {},
63
+ });
64
+ const result = reinjectImageSourcePaths(baseContent, "user", metadata);
65
+ expect(result).toBe(baseContent);
66
+ expect(result).toHaveLength(2);
67
+ });
68
+
69
+ test("handles multiple image source paths", () => {
70
+ const metadata = JSON.stringify({
71
+ imageSourcePaths: {
72
+ "a.jpg": "/path/to/a.jpg",
73
+ "b.png": "/path/to/b.png",
74
+ },
75
+ });
76
+ const result = reinjectImageSourcePaths(baseContent, "user", metadata);
77
+
78
+ expect(result).toHaveLength(3);
79
+ const annotation = result[2] as { type: "text"; text: string };
80
+ expect(annotation.type).toBe("text");
81
+ expect(annotation.text).toBe(
82
+ "[Attached image source: /path/to/a.jpg]\n[Attached image source: /path/to/b.png]",
83
+ );
84
+ });
85
+
86
+ test("gracefully handles malformed metadata JSON", () => {
87
+ const result = reinjectImageSourcePaths(
88
+ baseContent,
89
+ "user",
90
+ "not-valid-json{{{",
91
+ );
92
+ // Should return original content, not throw
93
+ expect(result).toBe(baseContent);
94
+ expect(result).toHaveLength(2);
95
+ });
96
+
97
+ test("filters out non-string values in imageSourcePaths", () => {
98
+ const metadata = JSON.stringify({
99
+ imageSourcePaths: {
100
+ "photo.jpg": "/Users/me/Desktop/photo.jpg",
101
+ "bad.jpg": 42,
102
+ "also_bad.jpg": null,
103
+ },
104
+ });
105
+ const result = reinjectImageSourcePaths(baseContent, "user", metadata);
106
+
107
+ expect(result).toHaveLength(3);
108
+ const annotation = result[2] as { type: "text"; text: string };
109
+ expect(annotation.text).toBe(
110
+ "[Attached image source: /Users/me/Desktop/photo.jpg]",
111
+ );
112
+ });
113
+
114
+ test("returns content unchanged when imageSourcePaths has only non-string values", () => {
115
+ const metadata = JSON.stringify({
116
+ imageSourcePaths: {
117
+ "bad.jpg": 42,
118
+ "also_bad.jpg": null,
119
+ },
120
+ });
121
+ const result = reinjectImageSourcePaths(baseContent, "user", metadata);
122
+ expect(result).toBe(baseContent);
123
+ expect(result).toHaveLength(2);
124
+ });
125
+
126
+ test("preserves original content blocks in returned array", () => {
127
+ const metadata = JSON.stringify({
128
+ imageSourcePaths: { "photo.jpg": "/path/photo.jpg" },
129
+ });
130
+ const result = reinjectImageSourcePaths(baseContent, "user", metadata);
131
+
132
+ // First two blocks should be identical to the originals
133
+ expect(result[0]).toEqual(baseContent[0]);
134
+ expect(result[1]).toEqual(baseContent[1]);
135
+ });
136
+ });