@vellumai/assistant 0.8.7 → 0.8.8

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 (387) hide show
  1. package/Dockerfile +20 -4
  2. package/docker-entrypoint.sh +4 -2
  3. package/docker-init-apt-root.sh +3 -1
  4. package/docker-kata-apt-env.sh +3 -1
  5. package/docker-kata-runtime-family.sh +12 -0
  6. package/docs/architecture/memory.md +1 -1
  7. package/docs/plugins.md +75 -79
  8. package/examples/plugins/echo/README.md +6 -12
  9. package/examples/plugins/echo/register.ts +0 -41
  10. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  11. package/openapi.yaml +3381 -348
  12. package/package.json +1 -1
  13. package/scripts/generate-openapi.ts +68 -41
  14. package/src/__tests__/agent-loop-exit-reason.test.ts +34 -39
  15. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  16. package/src/__tests__/agent-loop.test.ts +37 -87
  17. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  18. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  19. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  20. package/src/__tests__/anthropic-provider.test.ts +95 -2
  21. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  22. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  23. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  24. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  25. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  26. package/src/__tests__/btw-routes.test.ts +62 -3
  27. package/src/__tests__/build-persisted-content.test.ts +184 -0
  28. package/src/__tests__/catalog-files.test.ts +1 -1
  29. package/src/__tests__/clawhub-files.test.ts +1 -1
  30. package/src/__tests__/compaction-pipeline.test.ts +1 -1
  31. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  32. package/src/__tests__/config-watcher.test.ts +1 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  34. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -2
  35. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -4
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +313 -1136
  37. package/src/__tests__/conversation-agent-loop.test.ts +596 -1616
  38. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  39. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  40. package/src/__tests__/conversation-pairing.test.ts +4 -31
  41. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +26 -5
  43. package/src/__tests__/conversation-queue.test.ts +2 -0
  44. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  45. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +170 -229
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +3 -24
  48. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  49. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  50. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  51. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  52. package/src/__tests__/conversation-title-service.test.ts +135 -2
  53. package/src/__tests__/conversation-workspace-injection.test.ts +6 -1
  54. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  55. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  56. package/src/__tests__/dm-persistence.test.ts +5 -1
  57. package/src/__tests__/empty-response-hook.test.ts +304 -0
  58. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  59. package/src/__tests__/gemini-image-service.test.ts +13 -0
  60. package/src/__tests__/helpers/mock-provider.ts +110 -0
  61. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  62. package/src/__tests__/history-repair-hook.test.ts +1 -0
  63. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  64. package/src/__tests__/identity-routes.test.ts +248 -7
  65. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  66. package/src/__tests__/injector-background-turn.test.ts +2 -8
  67. package/src/__tests__/injector-chain.test.ts +106 -270
  68. package/src/__tests__/injector-disk-pressure.test.ts +3 -12
  69. package/src/__tests__/injector-document-comments.test.ts +2 -2
  70. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  71. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  72. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  73. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  74. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  75. package/src/__tests__/llm-usage-store.test.ts +223 -1
  76. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  77. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  78. package/src/__tests__/native-web-search.test.ts +191 -0
  79. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  80. package/src/__tests__/openai-image-service.test.ts +17 -0
  81. package/src/__tests__/openai-provider.test.ts +31 -1
  82. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  83. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  84. package/src/__tests__/pipeline-runner.test.ts +29 -39
  85. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  86. package/src/__tests__/plugin-bootstrap.test.ts +13 -28
  87. package/src/__tests__/plugin-registry.test.ts +0 -27
  88. package/src/__tests__/plugin-types.test.ts +2 -125
  89. package/src/__tests__/process-message-display-content.test.ts +6 -2
  90. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  91. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  92. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  93. package/src/__tests__/schedule-routes.test.ts +603 -2
  94. package/src/__tests__/schedule-store.test.ts +41 -0
  95. package/src/__tests__/schedule-tools.test.ts +35 -0
  96. package/src/__tests__/server-history-render.test.ts +314 -1
  97. package/src/__tests__/skillssh-files.test.ts +1 -1
  98. package/src/__tests__/system-prompt.test.ts +20 -0
  99. package/src/__tests__/task-scheduler.test.ts +162 -1
  100. package/src/__tests__/terminal-tools.test.ts +6 -1
  101. package/src/__tests__/title-generate-hook.test.ts +319 -0
  102. package/src/__tests__/tool-error-hook.test.ts +278 -0
  103. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  104. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  106. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  107. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  108. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  109. package/src/__tests__/usage-routes.test.ts +285 -1
  110. package/src/__tests__/user-plugin-loader.test.ts +2 -2
  111. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  112. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  113. package/src/agent/loop.ts +346 -442
  114. package/src/api/events/assistant-thinking-delta.ts +33 -0
  115. package/src/api/events/tool-output-chunk.ts +45 -0
  116. package/src/api/events/tool-use-preview-start.ts +32 -0
  117. package/src/api/events/trace-event.ts +69 -0
  118. package/src/api/index.ts +48 -13
  119. package/src/api/responses/conversation-message.ts +368 -0
  120. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  121. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  122. package/src/cli/commands/notifications.ts +112 -60
  123. package/src/config/assistant-feature-flags.ts +22 -11
  124. package/src/config/bundled-skills/app-builder/SKILL.md +3 -20
  125. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  126. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  127. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  128. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  129. package/src/config/bundled-skills/document-editor/SKILL.md +1 -1
  130. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  131. package/src/config/feature-flag-cache.ts +3 -3
  132. package/src/config/feature-flag-registry.json +35 -3
  133. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  134. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  135. package/src/config/schemas/llm.ts +1 -0
  136. package/src/config/schemas/memory-v2.ts +8 -0
  137. package/src/config/schemas/memory-v3.ts +8 -0
  138. package/src/config/schemas/platform.ts +8 -0
  139. package/src/config/seed-inference-profiles.ts +2 -2
  140. package/src/config/skills.ts +13 -0
  141. package/src/context/compactor.ts +1 -1
  142. package/src/context/strip-injections.ts +122 -0
  143. package/src/context/token-estimator.ts +23 -0
  144. package/src/context/tool-result-truncation.ts +0 -23
  145. package/src/context/window-manager.ts +3 -6
  146. package/src/credential-execution/executable-discovery.ts +16 -0
  147. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  148. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  149. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  150. package/src/daemon/assistant-attachments.ts +1 -1
  151. package/src/daemon/config-watcher.ts +2 -2
  152. package/src/daemon/context-overflow-reducer.ts +0 -1
  153. package/src/daemon/conversation-agent-loop-handlers.ts +605 -153
  154. package/src/daemon/conversation-agent-loop.ts +281 -760
  155. package/src/daemon/conversation-history.ts +5 -4
  156. package/src/daemon/conversation-lifecycle.ts +3 -4
  157. package/src/daemon/conversation-messaging.ts +7 -6
  158. package/src/daemon/conversation-process.ts +11 -16
  159. package/src/daemon/conversation-runtime-assembly.ts +130 -347
  160. package/src/daemon/conversation-slash.ts +6 -25
  161. package/src/daemon/conversation-surfaces.ts +222 -4
  162. package/src/daemon/conversation-tool-setup.ts +2 -29
  163. package/src/daemon/conversation.ts +32 -14
  164. package/src/daemon/external-plugins-bootstrap.ts +9 -10
  165. package/src/daemon/handlers/config-a2a.ts +51 -36
  166. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  167. package/src/daemon/handlers/config-telegram.ts +16 -2
  168. package/src/daemon/handlers/shared.ts +156 -84
  169. package/src/daemon/handlers/skills.ts +39 -10
  170. package/src/daemon/lifecycle.ts +4 -0
  171. package/src/daemon/message-types/apps.ts +1 -29
  172. package/src/daemon/message-types/messages.ts +9 -57
  173. package/src/daemon/message-types/skills.ts +2 -0
  174. package/src/daemon/message-types/surfaces.ts +136 -3
  175. package/src/daemon/now-scratchpad.ts +21 -0
  176. package/src/daemon/orphan-reaper.test.ts +210 -0
  177. package/src/daemon/orphan-reaper.ts +240 -0
  178. package/src/daemon/persist-unsendable-image.ts +117 -0
  179. package/src/daemon/process-message.ts +1 -3
  180. package/src/daemon/trace-emitter.ts +6 -4
  181. package/src/daemon/trust-context.ts +19 -0
  182. package/src/daemon/wake-target-adapter.ts +3 -1
  183. package/src/home/home-greeting-cache.ts +24 -1
  184. package/src/ipc/gateway-client.test.ts +2 -2
  185. package/src/ipc/gateway-client.ts +3 -3
  186. package/src/media/gemini-image-service.ts +15 -0
  187. package/src/media/openai-image-service.ts +14 -0
  188. package/src/media/types.ts +34 -0
  189. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  190. package/src/memory/auth-fallback-events-store.ts +94 -0
  191. package/src/memory/conversation-title-service.ts +65 -41
  192. package/src/memory/db-init.ts +4 -0
  193. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  194. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  195. package/src/memory/jobs-store.ts +33 -0
  196. package/src/memory/jobs-worker.ts +31 -4
  197. package/src/memory/llm-usage-store.ts +224 -50
  198. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  199. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  200. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  201. package/src/memory/migrations/index.ts +2 -0
  202. package/src/memory/pkb/autoinject.ts +61 -0
  203. package/src/memory/pkb/context.ts +50 -0
  204. package/src/memory/pkb/types.ts +14 -0
  205. package/src/memory/schedule-attribution-sql.ts +104 -0
  206. package/src/memory/schema/infrastructure.ts +16 -0
  207. package/src/memory/usage-grouped-buckets.ts +6 -1
  208. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -1
  209. package/src/memory/v2/consolidation-job.ts +1 -1
  210. package/src/memory/v3/__tests__/health.test.ts +16 -0
  211. package/src/memory/v3/__tests__/orchestrate.test.ts +45 -9
  212. package/src/memory/v3/__tests__/provider-blocks.test.ts +13 -0
  213. package/src/memory/v3/__tests__/router.test.ts +101 -29
  214. package/src/memory/v3/__tests__/selector.test.ts +93 -27
  215. package/src/memory/v3/__tests__/shadow-plugin.test.ts +23 -5
  216. package/src/memory/v3/health.ts +0 -0
  217. package/src/memory/v3/llm-retry.ts +32 -0
  218. package/src/memory/v3/orchestrate.ts +26 -14
  219. package/src/memory/v3/provider-blocks.ts +15 -5
  220. package/src/memory/v3/router.ts +48 -42
  221. package/src/memory/v3/selector.ts +57 -42
  222. package/src/memory/v3/shadow-plugin.ts +47 -15
  223. package/src/memory/v3/types.ts +8 -0
  224. package/src/notifications/conversation-pairing.ts +8 -15
  225. package/src/notifications/decision-engine.ts +6 -3
  226. package/src/notifications/home-feed-side-effect.ts +12 -1
  227. package/src/permissions/prompter.ts +4 -0
  228. package/src/plugin-api/constants.ts +4 -0
  229. package/src/plugin-api/index.ts +8 -1
  230. package/src/plugin-api/types.ts +151 -1
  231. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  232. package/src/plugins/defaults/empty-response/register.ts +8 -13
  233. package/src/plugins/defaults/index.ts +1 -15
  234. package/src/plugins/defaults/injectors/register.ts +243 -74
  235. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +91 -0
  236. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  237. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  238. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  239. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  240. package/src/plugins/defaults/title-generate/package.json +1 -1
  241. package/src/plugins/defaults/title-generate/register.ts +18 -18
  242. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  243. package/src/plugins/defaults/tool-error/package.json +1 -1
  244. package/src/plugins/defaults/tool-error/register.ts +9 -21
  245. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  246. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  247. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  248. package/src/plugins/pipeline.ts +6 -18
  249. package/src/plugins/registry.ts +8 -25
  250. package/src/plugins/types.ts +43 -474
  251. package/src/proactive-artifact/aux-message-injector.ts +3 -3
  252. package/src/proactive-artifact/job.test.ts +7 -12
  253. package/src/prompts/__tests__/system-prompt.test.ts +36 -0
  254. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +62 -0
  255. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  256. package/src/prompts/templates/system-sections.ts +15 -0
  257. package/src/providers/anthropic/client.ts +37 -29
  258. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  259. package/src/providers/openai/chat-completions-provider.ts +44 -0
  260. package/src/providers/openrouter/client.ts +1 -0
  261. package/src/providers/placeholder-sentinels.ts +35 -0
  262. package/src/runtime/__tests__/agent-wake.test.ts +5 -1
  263. package/src/runtime/agent-wake.ts +2 -2
  264. package/src/runtime/assistant-event-hub.ts +36 -6
  265. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  266. package/src/runtime/http-router.ts +16 -21
  267. package/src/runtime/http-types.ts +16 -70
  268. package/src/runtime/pending-interactions.ts +1 -0
  269. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  270. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  271. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  272. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  273. package/src/runtime/routes/app-management-routes.ts +6 -117
  274. package/src/runtime/routes/app-routes.ts +13 -15
  275. package/src/runtime/routes/attachment-routes.ts +26 -15
  276. package/src/runtime/routes/avatar-routes.ts +26 -0
  277. package/src/runtime/routes/btw-routes.ts +29 -23
  278. package/src/runtime/routes/consolidation-routes.ts +120 -20
  279. package/src/runtime/routes/conversation-query-routes.ts +2 -0
  280. package/src/runtime/routes/conversation-routes.ts +358 -184
  281. package/src/runtime/routes/documents-routes.ts +4 -0
  282. package/src/runtime/routes/domain-routes.ts +51 -37
  283. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  284. package/src/runtime/routes/events-routes.ts +28 -34
  285. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  286. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  287. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  288. package/src/runtime/routes/identity-routes.ts +208 -17
  289. package/src/runtime/routes/image-generation-routes.ts +40 -2
  290. package/src/runtime/routes/index.ts +2 -0
  291. package/src/runtime/routes/integrations/a2a.ts +12 -10
  292. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  293. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  294. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  295. package/src/runtime/routes/integrations/telegram.ts +6 -0
  296. package/src/runtime/routes/integrations/twilio.ts +42 -0
  297. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  298. package/src/runtime/routes/log-export-routes.ts +8 -0
  299. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  300. package/src/runtime/routes/memory-v3-routes.ts +50 -28
  301. package/src/runtime/routes/oauth-apps.ts +66 -12
  302. package/src/runtime/routes/oauth-providers.ts +44 -5
  303. package/src/runtime/routes/platform-routes.ts +81 -5
  304. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  305. package/src/runtime/routes/playground/force-compact.ts +1 -1
  306. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  307. package/src/runtime/routes/schedule-routes.ts +152 -42
  308. package/src/runtime/routes/secret-routes.ts +14 -2
  309. package/src/runtime/routes/skills-routes.ts +43 -14
  310. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  311. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  312. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  313. package/src/runtime/routes/tts-routes.ts +35 -0
  314. package/src/runtime/routes/types.ts +66 -8
  315. package/src/runtime/routes/usage-routes.ts +47 -39
  316. package/src/runtime/routes/webhook-routes.ts +41 -2
  317. package/src/runtime/routes/workspace-routes.ts +4 -0
  318. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  319. package/src/runtime/services/analyze-conversation.ts +2 -2
  320. package/src/schedule/schedule-store.ts +20 -1
  321. package/src/schedule/schedule-usage-store.ts +83 -0
  322. package/src/schedule/scheduler.ts +12 -5
  323. package/src/skills/catalog-files.ts +2 -2
  324. package/src/skills/catalog-install.ts +3 -0
  325. package/src/skills/categories-cache.ts +118 -0
  326. package/src/skills/clawhub-files.ts +1 -2
  327. package/src/skills/skillssh-files.ts +1 -2
  328. package/src/telemetry/types.ts +29 -1
  329. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  330. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  331. package/src/tools/executor.ts +1 -53
  332. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  333. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  334. package/src/tools/network/web-search-error.test.ts +248 -0
  335. package/src/tools/network/web-search-error.ts +267 -0
  336. package/src/tools/network/web-search.ts +207 -48
  337. package/src/tools/schedule/create.ts +2 -0
  338. package/src/tools/terminal/safe-env.ts +10 -1
  339. package/src/tools/ui-surface/definitions.ts +9 -1
  340. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  341. package/src/tts/provider-catalog.ts +76 -1
  342. package/src/util/mutex.ts +47 -0
  343. package/src/workspace/git-service.ts +1 -42
  344. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  345. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  346. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +93 -0
  347. package/src/workspace/migrations/registry.ts +6 -0
  348. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  349. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  350. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  351. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  352. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  353. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  354. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  355. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  356. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  357. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  358. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  359. package/src/gallery/default-gallery.ts +0 -1359
  360. package/src/gallery/gallery-manifest.ts +0 -28
  361. package/src/home/feature-gate.ts +0 -22
  362. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  363. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  364. package/src/plugins/defaults/injectors/package.json +0 -15
  365. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  366. package/src/plugins/defaults/llm-call/package.json +0 -15
  367. package/src/plugins/defaults/llm-call/register.ts +0 -45
  368. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  369. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  370. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  371. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  372. package/src/plugins/defaults/persistence/package.json +0 -15
  373. package/src/plugins/defaults/persistence/register.ts +0 -38
  374. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  375. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  376. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  377. package/src/plugins/defaults/token-estimate/package.json +0 -15
  378. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  379. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  380. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  381. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  382. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  383. package/src/plugins/defaults/tool-execute/package.json +0 -15
  384. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  385. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  386. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  387. package/src/skills/category-inference.ts +0 -111
@@ -24,7 +24,10 @@
24
24
  * once assistant output exists.
25
25
  */
26
26
 
27
- import { beforeEach, describe, expect, mock, test } from "bun:test";
27
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
28
+ import { tmpdir } from "node:os";
29
+ import { join } from "node:path";
30
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
28
31
 
29
32
  mock.module("../../../util/logger.js", () => ({
30
33
  getLogger: () =>
@@ -33,19 +36,28 @@ mock.module("../../../util/logger.js", () => ({
33
36
  }),
34
37
  }));
35
38
 
39
+ import { invalidateConfigCache } from "../../../config/loader.js";
36
40
  import { createConversation } from "../../../memory/conversation-crud.js";
37
41
  import { getDb } from "../../../memory/db-connection.js";
38
42
  import { initializeDb } from "../../../memory/db-init.js";
39
- import { rawRun } from "../../../memory/raw-query.js";
43
+ import { enqueueMemoryJob } from "../../../memory/jobs-store.js";
44
+ import { recordUsageEvent } from "../../../memory/llm-usage-store.js";
45
+ import { rawAll, rawRun } from "../../../memory/raw-query.js";
40
46
  import { ROUTES } from "../consolidation-routes.js";
41
47
  import type { RouteDefinition } from "../types.js";
42
48
 
43
49
  initializeDb();
44
50
 
51
+ let workspaceDir: string;
52
+ let origWorkspaceDir: string | undefined;
53
+ let configPath: string;
54
+
45
55
  function resetTables(): void {
46
56
  const db = getDb();
57
+ db.run(`DELETE FROM llm_usage_events`);
47
58
  db.run(`DELETE FROM messages`);
48
59
  db.run(`DELETE FROM conversations`);
60
+ db.run(`DELETE FROM memory_jobs`);
49
61
  }
50
62
 
51
63
  function findHandler(operationId: string): RouteDefinition["handler"] {
@@ -69,6 +81,59 @@ function insertMessage(
69
81
  );
70
82
  }
71
83
 
84
+ function recordUsageCostAt(
85
+ conversationId: string,
86
+ requestId: string,
87
+ createdAt: number,
88
+ estimatedCostUsd: number,
89
+ ): void {
90
+ const event = recordUsageEvent(
91
+ {
92
+ conversationId,
93
+ runId: null,
94
+ requestId,
95
+ actor: "main_agent",
96
+ callSite: "mainAgent",
97
+ inferenceProfile: "balanced",
98
+ provider: "anthropic",
99
+ model: "claude-sonnet-4-20250514",
100
+ inputTokens: 100,
101
+ outputTokens: 50,
102
+ cacheCreationInputTokens: 0,
103
+ cacheReadInputTokens: 0,
104
+ rawUsage: null,
105
+ },
106
+ { estimatedCostUsd, pricingStatus: "priced" },
107
+ );
108
+ rawRun(
109
+ "UPDATE llm_usage_events SET created_at = ? WHERE id = ?",
110
+ createdAt,
111
+ event.id,
112
+ );
113
+ }
114
+
115
+ function readConfig(): Record<string, unknown> {
116
+ return JSON.parse(readFileSync(configPath, "utf-8"));
117
+ }
118
+
119
+ function readMemoryJobRows(): Array<{
120
+ id: string;
121
+ status: string;
122
+ lastError: string | null;
123
+ payload: string;
124
+ }> {
125
+ return rawAll<{
126
+ id: string;
127
+ status: string;
128
+ lastError: string | null;
129
+ payload: string;
130
+ }>(`
131
+ SELECT id, status, last_error AS lastError, payload
132
+ FROM memory_jobs
133
+ ORDER BY id
134
+ `);
135
+ }
136
+
72
137
  interface RunRecord {
73
138
  id: string;
74
139
  scheduledFor: number;
@@ -79,6 +144,7 @@ interface RunRecord {
79
144
  skipReason: string | null;
80
145
  error: string | null;
81
146
  conversationId: string | null;
147
+ estimatedCostUsd: number;
82
148
  createdAt: number;
83
149
  }
84
150
 
@@ -88,9 +154,24 @@ interface ListRunsResponse {
88
154
 
89
155
  describe("listConsolidationRuns handler", () => {
90
156
  beforeEach(() => {
157
+ workspaceDir = mkdtempSync(join(tmpdir(), "vellum-consolidation-routes-"));
158
+ origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
159
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
160
+ configPath = join(workspaceDir, "config.json");
161
+ invalidateConfigCache();
91
162
  resetTables();
92
163
  });
93
164
 
165
+ afterEach(() => {
166
+ if (origWorkspaceDir === undefined) {
167
+ delete process.env.VELLUM_WORKSPACE_DIR;
168
+ } else {
169
+ process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
170
+ }
171
+ invalidateConfigCache();
172
+ rmSync(workspaceDir, { recursive: true, force: true });
173
+ });
174
+
94
175
  test("returns only conversations sourced from memory_v2_consolidation", async () => {
95
176
  createConversation({ title: "c1", source: "memory_v2_consolidation" });
96
177
  createConversation({ title: "h1", source: "heartbeat" });
@@ -138,6 +219,47 @@ describe("listConsolidationRuns handler", () => {
138
219
  expect(run.createdAt).toBe(1000);
139
220
  });
140
221
 
222
+ test("exposes estimatedCostUsd from the conversation total when available", async () => {
223
+ const conv = createConversation({
224
+ title: "c1",
225
+ source: "memory_v2_consolidation",
226
+ });
227
+ rawRun(
228
+ "UPDATE conversations SET created_at = ?, total_estimated_cost = ? WHERE id = ?",
229
+ 1000,
230
+ 0.42,
231
+ conv.id,
232
+ );
233
+ insertMessage(conv.id, "assistant", 2000);
234
+ recordUsageCostAt(conv.id, "consolidation-fallback-cost", 1500, 0.99);
235
+
236
+ const handler = findHandler("listConsolidationRuns");
237
+ const result = (await handler({})) as ListRunsResponse;
238
+
239
+ expect(result.runs[0]!.estimatedCostUsd).toBeCloseTo(0.42);
240
+ });
241
+
242
+ test("falls back to conversation-window usage cost when the total is empty", async () => {
243
+ const conv = createConversation({
244
+ title: "c1",
245
+ source: "memory_v2_consolidation",
246
+ });
247
+ rawRun(
248
+ "UPDATE conversations SET created_at = ? WHERE id = ?",
249
+ 1000,
250
+ conv.id,
251
+ );
252
+ insertMessage(conv.id, "assistant", 2000);
253
+ recordUsageCostAt(conv.id, "consolidation-before", 999, 0.4);
254
+ recordUsageCostAt(conv.id, "consolidation-inside", 1500, 0.07);
255
+ recordUsageCostAt(conv.id, "consolidation-after", 2001, 0.5);
256
+
257
+ const handler = findHandler("listConsolidationRuns");
258
+ const result = (await handler({})) as ListRunsResponse;
259
+
260
+ expect(result.runs[0]!.estimatedCostUsd).toBeCloseTo(0.07);
261
+ });
262
+
141
263
  test("synthesizes status='running' when conversation has no assistant message", async () => {
142
264
  createConversation({ title: "c1", source: "memory_v2_consolidation" });
143
265
 
@@ -256,3 +378,144 @@ describe("listConsolidationRuns handler", () => {
256
378
  expect(bad.runs).toHaveLength(5);
257
379
  });
258
380
  });
381
+
382
+ describe("updateConsolidationConfig handler", () => {
383
+ beforeEach(() => {
384
+ workspaceDir = mkdtempSync(join(tmpdir(), "vellum-consolidation-routes-"));
385
+ origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
386
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
387
+ configPath = join(workspaceDir, "config.json");
388
+ invalidateConfigCache();
389
+ resetTables();
390
+ });
391
+
392
+ afterEach(() => {
393
+ if (origWorkspaceDir === undefined) {
394
+ delete process.env.VELLUM_WORKSPACE_DIR;
395
+ } else {
396
+ process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
397
+ }
398
+ invalidateConfigCache();
399
+ rmSync(workspaceDir, { recursive: true, force: true });
400
+ });
401
+
402
+ test("persists only the consolidation enabled override without disabling memory v2", async () => {
403
+ writeFileSync(
404
+ configPath,
405
+ JSON.stringify(
406
+ {
407
+ memory: {
408
+ v2: {
409
+ enabled: true,
410
+ consolidation_interval_hours: 4,
411
+ },
412
+ },
413
+ },
414
+ null,
415
+ 2,
416
+ ) + "\n",
417
+ );
418
+
419
+ const handler = findHandler("updateConsolidationConfig");
420
+ const result = (await handler({ body: { enabled: false } })) as {
421
+ available: boolean;
422
+ enabled: boolean;
423
+ intervalMs: number;
424
+ nextRunAt: number | null;
425
+ success: boolean;
426
+ };
427
+
428
+ expect(readConfig()).toEqual({
429
+ memory: {
430
+ v2: {
431
+ enabled: true,
432
+ consolidation_interval_hours: 4,
433
+ consolidation_enabled: false,
434
+ },
435
+ },
436
+ });
437
+ expect(result).toMatchObject({
438
+ available: true,
439
+ enabled: false,
440
+ intervalMs: 4 * 60 * 60 * 1000,
441
+ nextRunAt: null,
442
+ success: true,
443
+ });
444
+ });
445
+
446
+ test("disabling automatic consolidation cancels pending automatic jobs but preserves manual run-now jobs", async () => {
447
+ writeFileSync(
448
+ configPath,
449
+ JSON.stringify(
450
+ {
451
+ memory: {
452
+ v2: {
453
+ enabled: true,
454
+ consolidation_enabled: true,
455
+ },
456
+ },
457
+ },
458
+ null,
459
+ 2,
460
+ ) + "\n",
461
+ );
462
+ const automaticJobId = enqueueMemoryJob("memory_v2_consolidate", {
463
+ trigger: "automatic",
464
+ });
465
+ const legacyJobId = enqueueMemoryJob("memory_v2_consolidate", {});
466
+ const manualJobId = enqueueMemoryJob("memory_v2_consolidate", {
467
+ trigger: "manual",
468
+ });
469
+
470
+ const handler = findHandler("updateConsolidationConfig");
471
+ await handler({ body: { enabled: false } });
472
+
473
+ const rowsById = new Map(
474
+ readMemoryJobRows().map((row) => [row.id, row] as const),
475
+ );
476
+ expect(rowsById.get(automaticJobId)).toMatchObject({
477
+ status: "failed",
478
+ lastError: "automatic_consolidation_disabled",
479
+ });
480
+ expect(rowsById.get(legacyJobId)).toMatchObject({
481
+ status: "failed",
482
+ lastError: "automatic_consolidation_disabled",
483
+ });
484
+ expect(rowsById.get(manualJobId)).toMatchObject({
485
+ status: "pending",
486
+ lastError: null,
487
+ payload: JSON.stringify({ trigger: "manual" }),
488
+ });
489
+ });
490
+
491
+ test("run-now remains available when automatic consolidation is disabled", async () => {
492
+ writeFileSync(
493
+ configPath,
494
+ JSON.stringify(
495
+ {
496
+ memory: {
497
+ v2: {
498
+ enabled: true,
499
+ consolidation_enabled: false,
500
+ },
501
+ },
502
+ },
503
+ null,
504
+ 2,
505
+ ) + "\n",
506
+ );
507
+
508
+ const handler = findHandler("runConsolidationNow");
509
+ const result = (await handler({})) as {
510
+ success: boolean;
511
+ ran: boolean;
512
+ jobId: string | null;
513
+ };
514
+
515
+ expect(result.success).toBe(true);
516
+ expect(result.ran).toBe(true);
517
+ expect(result.jobId).toBeString();
518
+ const row = readMemoryJobRows().find((job) => job.id === result.jobId);
519
+ expect(row?.payload).toBe(JSON.stringify({ trigger: "manual" }));
520
+ });
521
+ });
@@ -175,7 +175,10 @@ function seedRequestLog(
175
175
  .run();
176
176
  }
177
177
 
178
- function seedConversationKey(conversationKey: string, conversationId: string): void {
178
+ function seedConversationKey(
179
+ conversationKey: string,
180
+ conversationId: string,
181
+ ): void {
179
182
  getDb()
180
183
  .insert(conversationKeys)
181
184
  .values({
@@ -776,6 +779,33 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
776
779
  });
777
780
  });
778
781
 
782
+ test("saves a profile using the minimax provider (regression #32404)", async () => {
783
+ // minimax is exposed as a first-class provider in the catalog, so saving
784
+ // a profile bound to it must pass ProfileEntry validation rather than 400.
785
+ const result = await replaceProfileRoute.handler({
786
+ pathParams: { name: "custom" },
787
+ body: {
788
+ provider: "minimax",
789
+ model: "MiniMax-M2.7",
790
+ },
791
+ });
792
+
793
+ expect(result).toEqual({ ok: true });
794
+ const savedProfile = (
795
+ savedRawConfig?.llm as {
796
+ profiles: Record<string, Record<string, unknown>>;
797
+ }
798
+ ).profiles.custom;
799
+
800
+ expect(savedProfile.provider).toBe("minimax");
801
+ expect(savedProfile.model).toBe("MiniMax-M2.7");
802
+ expect(savedProfile.provider_connection).toBe("minimax-personal");
803
+
804
+ const conn = getConnection(getDb(), "minimax-personal");
805
+ expect(conn).not.toBeNull();
806
+ expect(conn!.provider).toBe("minimax");
807
+ });
808
+
779
809
  describe("managed profile guard", () => {
780
810
  beforeEach(() => {
781
811
  // Seed a managed profile alongside the existing custom one.
@@ -155,7 +155,11 @@ describe("memory_v2_ema_scores route registration", () => {
155
155
  expect(route!.tags).toContain("memory");
156
156
  // Schema rejects unknown keys so accidental request-body fields fail
157
157
  // loudly during route adapter parsing rather than silently propagating.
158
- expect(() => route!.requestBody!.parse({ extra: true })).toThrow();
159
- expect(() => route!.requestBody!.parse({})).not.toThrow();
158
+ const requestBody = route!.requestBody;
159
+ if (!requestBody || !("parse" in requestBody)) {
160
+ throw new Error("expected a JSON (Zod) request-body schema");
161
+ }
162
+ expect(() => requestBody.parse({ extra: true })).toThrow();
163
+ expect(() => requestBody.parse({})).not.toThrow();
160
164
  });
161
165
  });
@@ -164,8 +164,12 @@ afterEach(() => {
164
164
  // ---------------------------------------------------------------------------
165
165
 
166
166
  describe("tts-routes", () => {
167
- test("exports route definitions for messages/:messageId/tts, tts/synthesize, and tts/synthesize-cli", () => {
168
- expect(ROUTES).toHaveLength(3);
167
+ test("exports route definitions for tts/providers, messages/:messageId/tts, tts/synthesize, and tts/synthesize-cli", () => {
168
+ expect(ROUTES).toHaveLength(4);
169
+
170
+ const providers = getRoute("tts/providers");
171
+ expect(providers.method).toBe("GET");
172
+ expect(providers.policy?.requiredScopes).toContain("settings.read");
169
173
 
170
174
  const msgTts = getRoute("messages/:messageId/tts");
171
175
  expect(msgTts.method).toBe("POST");
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Route handlers for app CRUD, bundling, sharing, versioning,
3
- * gallery, and signing operations.
3
+ * and signing operations.
4
4
  */
5
5
  import { randomBytes } from "node:crypto";
6
6
  import {
@@ -12,7 +12,7 @@ import {
12
12
  } from "node:fs";
13
13
  import { stat, unlink } from "node:fs/promises";
14
14
  import { homedir, tmpdir } from "node:os";
15
- import { dirname, join } from "node:path";
15
+ import { join } from "node:path";
16
16
 
17
17
  import { z } from "zod";
18
18
 
@@ -22,7 +22,6 @@ import { scanBundle } from "../../bundler/bundle-scanner.js";
22
22
  import type { SignatureJson } from "../../bundler/bundle-signer.js";
23
23
  import { verifyBundleSignature } from "../../bundler/signature-verifier.js";
24
24
  import { compareSemver } from "../../daemon/handlers/shared.js";
25
- import { defaultGallery } from "../../gallery/default-gallery.js";
26
25
  import {
27
26
  getAppDiff,
28
27
  getAppHistory,
@@ -272,47 +271,6 @@ function forkSharedApp(
272
271
  return { success: true, appId: newApp.id, name: newApp.name };
273
272
  }
274
273
 
275
- async function installGalleryApp(
276
- galleryAppId: string,
277
- ): Promise<
278
- | { success: true; appId: string; name: string }
279
- | { success: false; error: string }
280
- > {
281
- const galleryApp = defaultGallery.apps.find((a) => a.id === galleryAppId);
282
- if (!galleryApp) {
283
- return {
284
- success: false,
285
- error: `Gallery app not found: ${galleryAppId}`,
286
- };
287
- }
288
-
289
- const app = createApp({
290
- name: galleryApp.name,
291
- description: galleryApp.description,
292
- schemaJson: galleryApp.schemaJson,
293
- htmlDefinition: galleryApp.htmlDefinition,
294
- formatVersion: galleryApp.formatVersion,
295
- });
296
-
297
- if (galleryApp.formatVersion === 2 && galleryApp.sourceFiles) {
298
- const appDir = getAppDirPath(app.id);
299
- for (const [relPath, content] of Object.entries(galleryApp.sourceFiles)) {
300
- const fullPath = join(appDir, relPath);
301
- mkdirSync(dirname(fullPath), { recursive: true });
302
- writeFileSync(fullPath, content, "utf-8");
303
- }
304
- const result = await compileApp(appDir);
305
- if (!result.ok) {
306
- log.warn(
307
- { appId: app.id, errors: result.errors },
308
- "Gallery app compilation had errors; falling back to htmlDefinition",
309
- );
310
- }
311
- }
312
-
313
- return { success: true, appId: app.id, name: app.name };
314
- }
315
-
316
274
  async function openBundle(filePath: string): Promise<Record<string, unknown>> {
317
275
  const fileStat = await stat(filePath);
318
276
  const bundleSizeBytes = fileStat.size;
@@ -594,22 +552,6 @@ function handleForkSharedApp({ body, headers }: RouteHandlerArgs) {
594
552
  return result;
595
553
  }
596
554
 
597
- async function handleInstallGalleryApp({ body, headers }: RouteHandlerArgs) {
598
- if (!body?.galleryAppId) {
599
- throw new BadRequestError("galleryAppId is required");
600
- }
601
- const result = await installGalleryApp(body.galleryAppId as string);
602
- if (!result.success) {
603
- throw new BadRequestError(result.error);
604
- }
605
- publishAppsChanged(getOriginClientId(headers));
606
- return result;
607
- }
608
-
609
- function handleListGallery() {
610
- return { gallery: defaultGallery };
611
- }
612
-
613
555
  function handleSignBundle({ body }: RouteHandlerArgs) {
614
556
  if (!body?.payload) {
615
557
  throw new BadRequestError("payload is required");
@@ -900,62 +842,6 @@ export const ROUTES: RouteDefinition[] = [
900
842
  name: z.string(),
901
843
  }),
902
844
  },
903
- {
904
- operationId: "apps_gallery_install",
905
- endpoint: "apps/gallery/install",
906
- method: "POST",
907
- policy: {
908
- requiredScopes: ["settings.write"],
909
- allowedPrincipalTypes: ACTOR_PRINCIPALS,
910
- },
911
- handler: handleInstallGalleryApp,
912
- summary: "Install a gallery app",
913
- description: "Install an app from the built-in gallery by its ID.",
914
- tags: ["apps"],
915
- requestBody: z.object({ galleryAppId: z.string() }),
916
- responseBody: z.object({
917
- success: z.literal(true),
918
- appId: z.string(),
919
- name: z.string(),
920
- }),
921
- },
922
- {
923
- operationId: "apps_gallery_list",
924
- endpoint: "apps/gallery",
925
- method: "GET",
926
- policy: {
927
- requiredScopes: ["settings.read"],
928
- allowedPrincipalTypes: ACTOR_PRINCIPALS,
929
- },
930
- handler: handleListGallery,
931
- summary: "List gallery apps",
932
- description: "Return the built-in app gallery catalog.",
933
- tags: ["apps"],
934
- responseBody: z.object({
935
- gallery: z.object({
936
- version: z.number(),
937
- updatedAt: z.string(),
938
- categories: z.array(
939
- z.object({
940
- id: z.string(),
941
- name: z.string(),
942
- icon: z.string(),
943
- }),
944
- ),
945
- apps: z.array(
946
- z.object({
947
- id: z.string(),
948
- name: z.string(),
949
- description: z.string(),
950
- icon: z.string(),
951
- category: z.string(),
952
- version: z.string(),
953
- featured: z.boolean().optional(),
954
- }),
955
- ),
956
- }),
957
- }),
958
- },
959
845
  {
960
846
  operationId: "apps_import_bundle",
961
847
  endpoint: "apps/import-bundle",
@@ -969,7 +855,10 @@ export const ROUTES: RouteDefinition[] = [
969
855
  description:
970
856
  "Upload, validate, and install a .vbundle archive as a new local app.",
971
857
  tags: ["apps"],
972
- rawBody: true,
858
+ requestBody: {
859
+ contentType: "application/octet-stream",
860
+ schema: { type: "string", format: "binary" },
861
+ },
973
862
  responseBody: z.object({
974
863
  success: z.boolean(),
975
864
  appId: z.string(),
@@ -73,14 +73,15 @@ function buildCsp(scriptSrc: string): string {
73
73
  ].join("; ");
74
74
  }
75
75
 
76
- function servePageHeaders({ pathParams }: ResponseHeaderArgs): Record<string, string> {
76
+ function servePageHeaders({
77
+ pathParams,
78
+ }: ResponseHeaderArgs): Record<string, string> {
77
79
  const appId = pathParams?.appId as string;
78
80
  const app = getApp(appId);
79
81
  // Multifile apps use external scripts — no 'unsafe-inline' for script-src.
80
82
  // Legacy apps contain inline event handlers that require 'unsafe-inline'.
81
- const scriptSrc = app && isMultifileApp(app)
82
- ? "'self'"
83
- : "'self' 'unsafe-inline'";
83
+ const scriptSrc =
84
+ app && isMultifileApp(app) ? "'self'" : "'self' 'unsafe-inline'";
84
85
  return {
85
86
  "Content-Type": "text/html; charset=utf-8",
86
87
  "Content-Security-Policy": buildCsp(scriptSrc),
@@ -186,9 +187,7 @@ const DIST_CONTENT_TYPES: Record<string, string> = {
186
187
  * Serve a static file from an app's dist/ directory.
187
188
  * Validates the filename to prevent path traversal.
188
189
  */
189
- function handleServeDistFile({
190
- pathParams,
191
- }: RouteHandlerArgs): Uint8Array {
190
+ function handleServeDistFile({ pathParams }: RouteHandlerArgs): Uint8Array {
192
191
  const appId = pathParams?.appId as string;
193
192
  const filename = pathParams?.filename as string;
194
193
 
@@ -225,9 +224,7 @@ function handleServeDistFile({
225
224
  /** 50 MB — generous cap for zip app bundles. */
226
225
  const MAX_SHARE_BODY_BYTES = 50 * 1024 * 1024;
227
226
 
228
- async function handleShareApp({
229
- rawBody,
230
- }: RouteHandlerArgs): Promise<{
227
+ async function handleShareApp({ rawBody }: RouteHandlerArgs): Promise<{
231
228
  shareToken: string;
232
229
  shareUrl: string;
233
230
  bundleSizeBytes: number;
@@ -275,9 +272,7 @@ async function handleShareApp({
275
272
  };
276
273
  }
277
274
 
278
- function handleDownloadSharedApp({
279
- pathParams,
280
- }: RouteHandlerArgs): Uint8Array {
275
+ function handleDownloadSharedApp({ pathParams }: RouteHandlerArgs): Uint8Array {
281
276
  const shareToken = pathParams?.token as string;
282
277
  const record = getSharedAppLink(shareToken);
283
278
  if (!record) {
@@ -348,8 +343,7 @@ export const ROUTES: RouteDefinition[] = [
348
343
  allowedPrincipalTypes: ACTOR_PRINCIPALS,
349
344
  },
350
345
  summary: "Serve app dist file",
351
- description:
352
- "Serve a static asset from an app's compiled dist/ directory.",
346
+ description: "Serve a static asset from an app's compiled dist/ directory.",
353
347
  tags: ["apps"],
354
348
  responseHeaders: ({ pathParams }) => ({
355
349
  "Content-Type":
@@ -411,6 +405,10 @@ export const ROUTES: RouteDefinition[] = [
411
405
  "Content-Type": "application/zip",
412
406
  "Content-Disposition": 'attachment; filename="app.vellum"',
413
407
  },
408
+ responseBody: {
409
+ contentType: "application/zip",
410
+ schema: { type: "string", format: "binary" },
411
+ },
414
412
  handler: handleDownloadSharedApp,
415
413
  },
416
414
  {
@@ -663,6 +663,10 @@ export const ROUTES: RouteDefinition[] = [
663
663
  "Serve raw file bytes for an attachment. Supports Range headers.",
664
664
  tags: ["attachments"],
665
665
  responseStatus: ({ headers }) => (headers?.["range"] ? "206" : "200"),
666
+ responseBody: {
667
+ contentType: "application/octet-stream",
668
+ schema: { type: "string", format: "binary" },
669
+ },
666
670
  additionalResponses: {
667
671
  "416": { description: "Range Not Satisfiable" },
668
672
  },
@@ -719,21 +723,28 @@ export const ROUTES: RouteDefinition[] = [
719
723
  description:
720
724
  "Upload an attachment. Supports application/json (base64 data or file path reference), multipart/form-data (file + filename + mimeType fields), and application/octet-stream (raw bytes with filename and mimeType query params).",
721
725
  tags: ["attachments"],
722
- requestBody: z.object({
723
- filename: z.string(),
724
- mimeType: z.string(),
725
- data: z.string().describe("Base64-encoded file data").optional(),
726
- filePath: z
727
- .string()
728
- .describe("On-disk file path (file-backed upload)")
729
- .optional(),
730
- trustedSource: z
731
- .boolean()
732
- .describe(
733
- "Set by the gateway when the file came from a guardian-bound channel actor. Honored only when the request is authenticated as a gateway service token; ignored otherwise.",
734
- )
735
- .optional(),
736
- }),
726
+ // Advertised as multipart/form-data — the form web clients actually send,
727
+ // so the generated SDK types a real `{ file, filename, mimeType }` body
728
+ // instead of erasing it. The handler additionally accepts application/json
729
+ // (base64/file-path) and application/octet-stream by sniffing the request
730
+ // Content-Type header (see handleUploadAttachmentRoute); those forms are
731
+ // used over raw HTTP by the gateway and are not consumed via the SDK.
732
+ requestBody: {
733
+ contentType: "multipart/form-data",
734
+ schema: {
735
+ type: "object",
736
+ properties: {
737
+ file: {
738
+ type: "string",
739
+ format: "binary",
740
+ description: "The file to upload",
741
+ },
742
+ filename: { type: "string" },
743
+ mimeType: { type: "string" },
744
+ },
745
+ required: ["file", "filename", "mimeType"],
746
+ },
747
+ },
737
748
  responseBody: z.object({
738
749
  id: z.string(),
739
750
  original_filename: z.string(),