@vellumai/assistant 0.4.56 → 0.5.0

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 (457) hide show
  1. package/ARCHITECTURE.md +10 -10
  2. package/Dockerfile +3 -0
  3. package/README.md +11 -11
  4. package/docs/architecture/integrations.md +2 -2
  5. package/docs/architecture/memory.md +3 -4
  6. package/docs/credential-execution-service.md +13 -20
  7. package/node_modules/@vellumai/ces-contracts/src/error.ts +5 -4
  8. package/package.json +1 -1
  9. package/src/__tests__/actor-token-service.test.ts +7 -7
  10. package/src/__tests__/anthropic-provider.test.ts +172 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +15 -1
  12. package/src/__tests__/approval-cascade.test.ts +2 -2
  13. package/src/__tests__/approval-routes-http.test.ts +3 -4
  14. package/src/__tests__/asset-materialize-tool.test.ts +5 -5
  15. package/src/__tests__/asset-search-tool.test.ts +1 -1
  16. package/src/__tests__/assistant-attachments.test.ts +5 -5
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +50 -38
  19. package/src/__tests__/attachments-store.test.ts +2 -2
  20. package/src/__tests__/avatar-e2e.test.ts +5 -3
  21. package/src/__tests__/browser-skill-endstate.test.ts +0 -1
  22. package/src/__tests__/call-routes-http.test.ts +2 -2
  23. package/src/__tests__/callback-handoff-copy.test.ts +1 -1
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +158 -0
  25. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  26. package/src/__tests__/channel-readiness-service.test.ts +0 -1
  27. package/src/__tests__/checker.test.ts +31 -32
  28. package/src/__tests__/chrome-cdp.test.ts +47 -18
  29. package/src/__tests__/claude-code-skill-regression.test.ts +2 -2
  30. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  31. package/src/__tests__/config-schema.test.ts +9 -18
  32. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -4
  34. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -2
  35. package/src/__tests__/conversation-agent-loop.test.ts +11 -4
  36. package/src/__tests__/conversation-attachments.test.ts +1 -1
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -2
  38. package/src/__tests__/conversation-error.test.ts +33 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +0 -1
  40. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  41. package/src/__tests__/conversation-pairing.test.ts +1 -1
  42. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -4
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +4 -4
  44. package/src/__tests__/conversation-queue.test.ts +23 -14
  45. package/src/__tests__/conversation-routes-slash-commands.test.ts +3 -3
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +204 -185
  47. package/src/__tests__/conversation-seed-composer.test.ts +1 -1
  48. package/src/__tests__/conversation-slash-queue.test.ts +4 -4
  49. package/src/__tests__/conversation-slash-unknown.test.ts +4 -4
  50. package/src/__tests__/conversation-starter-routes.test.ts +291 -0
  51. package/src/__tests__/conversation-wipe.test.ts +438 -0
  52. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -3
  53. package/src/__tests__/conversation-workspace-injection.test.ts +4 -5
  54. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -5
  55. package/src/__tests__/credential-security-e2e.test.ts +20 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault-unit.test.ts +227 -0
  58. package/src/__tests__/credentials-cli.test.ts +3 -0
  59. package/src/__tests__/date-context.test.ts +59 -377
  60. package/src/__tests__/drop-capability-card-state-migration.test.ts +169 -0
  61. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +11 -45
  62. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -3
  63. package/src/__tests__/encrypted-store.test.ts +249 -15
  64. package/src/__tests__/ephemeral-permissions.test.ts +4 -5
  65. package/src/__tests__/event-bus.test.ts +3 -3
  66. package/src/__tests__/file-read-tool.test.ts +40 -0
  67. package/src/__tests__/gateway-only-enforcement.test.ts +2 -2
  68. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  69. package/src/__tests__/gemini-image-service.test.ts +4 -4
  70. package/src/__tests__/gemini-provider.test.ts +6 -9
  71. package/src/__tests__/guardian-binding-drift-heal.test.ts +128 -0
  72. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  73. package/src/__tests__/host-file-read-tool.test.ts +87 -0
  74. package/src/__tests__/host-shell-tool.test.ts +6 -6
  75. package/src/__tests__/http-user-message-parity.test.ts +2 -2
  76. package/src/__tests__/identity-intro-cache.test.ts +209 -0
  77. package/src/__tests__/intent-routing.test.ts +51 -99
  78. package/src/__tests__/invite-routes-http.test.ts +5 -0
  79. package/src/__tests__/list-messages-attachments.test.ts +1 -1
  80. package/src/__tests__/managed-proxy-context.test.ts +2 -5
  81. package/src/__tests__/managed-skill-lifecycle.test.ts +8 -8
  82. package/src/__tests__/media-generate-image.test.ts +32 -15
  83. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  84. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +1 -1
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +24 -18
  86. package/src/__tests__/memory-recall-quality.test.ts +4 -3
  87. package/src/__tests__/memory-regressions.test.ts +86 -90
  88. package/src/__tests__/migration-cross-version-compatibility.test.ts +32 -32
  89. package/src/__tests__/migration-export-http.test.ts +26 -27
  90. package/src/__tests__/migration-import-commit-http.test.ts +165 -37
  91. package/src/__tests__/migration-import-preflight-http.test.ts +81 -20
  92. package/src/__tests__/migration-validate-http.test.ts +16 -16
  93. package/src/__tests__/model-intents.test.ts +2 -2
  94. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +1 -1
  95. package/src/__tests__/non-member-access-request.test.ts +3 -3
  96. package/src/__tests__/notification-broadcaster.test.ts +1 -1
  97. package/src/__tests__/notification-decision-fallback.test.ts +2 -2
  98. package/src/__tests__/notification-decision-identity.test.ts +8 -9
  99. package/src/__tests__/notification-decision-strategy.test.ts +1 -1
  100. package/src/__tests__/notification-deep-link.test.ts +1 -1
  101. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  102. package/src/__tests__/notification-schedule-dedup.test.ts +7 -7
  103. package/src/__tests__/oauth-store.test.ts +1 -3
  104. package/src/__tests__/oauth2-gateway-transport.test.ts +6 -1
  105. package/src/__tests__/onboarding-template-contract.test.ts +23 -59
  106. package/src/__tests__/provider-error-scenarios.test.ts +154 -0
  107. package/src/__tests__/provider-fail-open-selection.test.ts +2 -2
  108. package/src/__tests__/provider-managed-proxy-integration.test.ts +8 -9
  109. package/src/__tests__/provider-registry-ollama.test.ts +5 -2
  110. package/src/__tests__/qdrant-manager.test.ts +7 -7
  111. package/src/__tests__/ratelimit.test.ts +0 -74
  112. package/src/__tests__/recording-handler.test.ts +0 -1
  113. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  115. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
  116. package/src/__tests__/runtime-events-sse.test.ts +1 -1
  117. package/src/__tests__/scheduler-recurrence.test.ts +46 -2
  118. package/src/__tests__/schema-transforms.test.ts +114 -54
  119. package/src/__tests__/secret-onetime-send.test.ts +20 -0
  120. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -2
  121. package/src/__tests__/secret-scanner-executor.test.ts +1 -2
  122. package/src/__tests__/send-endpoint-busy.test.ts +63 -4
  123. package/src/__tests__/send-notification-tool.test.ts +2 -2
  124. package/src/__tests__/shell-credential-ref.test.ts +0 -1
  125. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -2
  126. package/src/__tests__/skill-memory.test.ts +549 -0
  127. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -2
  128. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -0
  129. package/src/__tests__/slack-channel-config.test.ts +109 -94
  130. package/src/__tests__/swarm-conversation-integration.test.ts +2 -2
  131. package/src/__tests__/swarm-recursion.test.ts +2 -2
  132. package/src/__tests__/swarm-tool.test.ts +2 -2
  133. package/src/__tests__/system-prompt.test.ts +19 -66
  134. package/src/__tests__/telegram-config.test.ts +121 -0
  135. package/src/__tests__/terminal-tools.test.ts +1 -1
  136. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -2
  137. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  138. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
  139. package/src/__tests__/tool-executor.test.ts +1 -1
  140. package/src/__tests__/trace-emitter.test.ts +8 -1
  141. package/src/__tests__/trust-store.test.ts +7 -8
  142. package/src/__tests__/twilio-routes.test.ts +1 -18
  143. package/src/__tests__/user-reference.test.ts +82 -2
  144. package/src/__tests__/vbundle-pax-and-symlink.test.ts +196 -0
  145. package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
  146. package/src/approvals/guardian-request-resolvers.ts +3 -3
  147. package/src/avatar/ascii-renderer.ts +2 -2
  148. package/src/avatar/png-renderer.ts +2 -2
  149. package/src/avatar/resvg-lazy.ts +21 -0
  150. package/src/calls/guardian-dispatch.ts +1 -1
  151. package/src/calls/relay-access-wait.ts +2 -2
  152. package/src/calls/twilio-rest.ts +0 -248
  153. package/src/cli/AGENTS.md +5 -8
  154. package/src/cli/__tests__/notifications.test.ts +5 -5
  155. package/src/cli/commands/avatar.ts +64 -2
  156. package/src/cli/commands/conversations.ts +131 -1
  157. package/src/cli/commands/credentials.ts +2 -0
  158. package/src/cli/commands/notifications.ts +3 -3
  159. package/src/cli.ts +10 -0
  160. package/src/config/bundled-skills/acp/SKILL.md +5 -5
  161. package/src/config/bundled-skills/acp/TOOLS.json +6 -6
  162. package/src/config/bundled-skills/app-builder/SKILL.md +42 -42
  163. package/src/config/bundled-skills/app-builder/TOOLS.json +10 -10
  164. package/src/config/bundled-skills/browser/SKILL.md +15 -15
  165. package/src/config/bundled-skills/browser/TOOLS.json +14 -14
  166. package/src/config/bundled-skills/chatgpt-import/SKILL.md +2 -2
  167. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +1 -1
  168. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  169. package/src/config/bundled-skills/claude-code/SKILL.md +5 -5
  170. package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
  171. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -15
  172. package/src/config/bundled-skills/contacts/SKILL.md +3 -3
  173. package/src/config/bundled-skills/contacts/TOOLS.json +4 -4
  174. package/src/config/bundled-skills/document/SKILL.md +4 -4
  175. package/src/config/bundled-skills/document/TOOLS.json +2 -2
  176. package/src/config/bundled-skills/followups/TOOLS.json +3 -3
  177. package/src/config/bundled-skills/gmail/SKILL.md +32 -32
  178. package/src/config/bundled-skills/gmail/TOOLS.json +16 -16
  179. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +1 -1
  180. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  181. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
  182. package/src/config/bundled-skills/google-calendar/TOOLS.json +5 -5
  183. package/src/config/bundled-skills/google-calendar/types.ts +1 -1
  184. package/src/config/bundled-skills/heartbeat/SKILL.md +43 -0
  185. package/src/config/bundled-skills/image-studio/SKILL.md +3 -3
  186. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -3
  187. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +16 -12
  188. package/src/config/bundled-skills/media-processing/SKILL.md +40 -40
  189. package/src/config/bundled-skills/media-processing/TOOLS.json +8 -8
  190. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +2 -2
  191. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +1 -1
  192. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +5 -5
  193. package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -2
  194. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -2
  195. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -2
  196. package/src/config/bundled-skills/media-processing/services/reduce.ts +3 -3
  197. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  198. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +1 -1
  199. package/src/config/bundled-skills/messaging/SKILL.md +29 -25
  200. package/src/config/bundled-skills/messaging/TOOLS.json +11 -11
  201. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  202. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  203. package/src/config/bundled-skills/notifications/SKILL.md +3 -3
  204. package/src/config/bundled-skills/notifications/TOOLS.json +2 -2
  205. package/src/config/bundled-skills/notifications/tools/send-notification.ts +3 -3
  206. package/src/config/bundled-skills/orchestration/SKILL.md +1 -1
  207. package/src/config/bundled-skills/orchestration/TOOLS.json +1 -1
  208. package/src/config/bundled-skills/phone-calls/SKILL.md +18 -14
  209. package/src/config/bundled-skills/phone-calls/TOOLS.json +3 -3
  210. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +2 -2
  211. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +2 -2
  212. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +1 -1
  213. package/src/config/bundled-skills/playbooks/TOOLS.json +4 -4
  214. package/src/config/bundled-skills/schedule/SKILL.md +26 -26
  215. package/src/config/bundled-skills/schedule/TOOLS.json +5 -5
  216. package/src/config/bundled-skills/screen-watch/SKILL.md +3 -3
  217. package/src/config/bundled-skills/screen-watch/TOOLS.json +1 -1
  218. package/src/config/bundled-skills/sequences/SKILL.md +2 -2
  219. package/src/config/bundled-skills/sequences/TOOLS.json +10 -10
  220. package/src/config/bundled-skills/sequences/tools/sequence-analytics.ts +2 -2
  221. package/src/config/bundled-skills/sequences/tools/sequence-enroll.ts +2 -2
  222. package/src/config/bundled-skills/sequences/tools/sequence-enrollment-list.ts +1 -1
  223. package/src/config/bundled-skills/sequences/tools/sequence-get.ts +1 -1
  224. package/src/config/bundled-skills/sequences/tools/sequence-import.ts +3 -3
  225. package/src/config/bundled-skills/sequences/tools/sequence-list.ts +1 -1
  226. package/src/config/bundled-skills/sequences/tools/sequence-update.ts +1 -1
  227. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  228. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
  229. package/src/config/bundled-skills/skill-management/TOOLS.json +5 -5
  230. package/src/config/bundled-skills/skills-catalog/SKILL.md +84 -0
  231. package/src/config/bundled-skills/slack/SKILL.md +2 -2
  232. package/src/config/bundled-skills/slack/TOOLS.json +8 -8
  233. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +3 -3
  234. package/src/config/bundled-skills/subagent/TOOLS.json +5 -5
  235. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  236. package/src/config/bundled-skills/tasks/TOOLS.json +9 -9
  237. package/src/config/bundled-skills/transcribe/SKILL.md +5 -5
  238. package/src/config/bundled-skills/transcribe/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +10 -10
  240. package/src/config/bundled-skills/watcher/SKILL.md +4 -4
  241. package/src/config/bundled-skills/watcher/TOOLS.json +5 -5
  242. package/src/config/feature-flag-registry.json +33 -17
  243. package/src/config/schemas/sandbox.ts +1 -1
  244. package/src/config/schemas/services.ts +13 -3
  245. package/src/config/schemas/timeouts.ts +0 -10
  246. package/src/contacts/contact-store.ts +63 -0
  247. package/src/contacts/contacts-write.ts +1 -1
  248. package/src/daemon/assistant-attachments.ts +2 -2
  249. package/src/daemon/conversation-agent-loop-handlers.ts +2 -2
  250. package/src/daemon/conversation-agent-loop.ts +7 -30
  251. package/src/daemon/conversation-error.ts +24 -0
  252. package/src/daemon/conversation-memory.ts +8 -7
  253. package/src/daemon/conversation-runtime-assembly.ts +141 -275
  254. package/src/daemon/conversation-slash.ts +7 -26
  255. package/src/daemon/conversation-surfaces.ts +14 -0
  256. package/src/daemon/conversation-tool-setup.ts +9 -8
  257. package/src/daemon/conversation.ts +2 -0
  258. package/src/daemon/daemon-control.ts +1 -1
  259. package/src/daemon/date-context.ts +10 -83
  260. package/src/daemon/handlers/config-channels.ts +12 -2
  261. package/src/daemon/handlers/config-slack-channel.ts +7 -1
  262. package/src/daemon/handlers/config-telegram.ts +6 -1
  263. package/src/daemon/handlers/conversations.ts +2 -2
  264. package/src/daemon/handlers/skills.ts +4 -0
  265. package/src/daemon/lifecycle.ts +28 -4
  266. package/src/daemon/providers-setup.ts +1 -1
  267. package/src/daemon/server.ts +1 -5
  268. package/src/daemon/shutdown-handlers.ts +9 -3
  269. package/src/daemon/tool-side-effects.ts +40 -0
  270. package/src/daemon/trace-emitter.ts +26 -2
  271. package/src/events/domain-events.ts +1 -1
  272. package/src/events/tool-permission-telemetry-listener.ts +46 -0
  273. package/src/inbound/platform-callback-registration.ts +0 -18
  274. package/src/media/app-icon-generator.ts +15 -8
  275. package/src/media/avatar-router.ts +15 -8
  276. package/src/media/gemini-image-service.ts +125 -21
  277. package/src/memory/attachments-store.ts +3 -3
  278. package/src/memory/channel-verification-sessions.ts +6 -6
  279. package/src/memory/conversation-crud.ts +196 -1
  280. package/src/memory/{thread-starters-cadence.ts → conversation-starters-cadence.ts} +9 -42
  281. package/src/memory/conversation-title-service.ts +2 -3
  282. package/src/memory/db-init.ts +25 -1
  283. package/src/memory/invite-store.ts +4 -4
  284. package/src/memory/items-extractor.ts +4 -4
  285. package/src/memory/job-handlers/{thread-starters.ts → conversation-starters.ts} +123 -38
  286. package/src/memory/jobs-store.ts +3 -2
  287. package/src/memory/jobs-worker.ts +7 -5
  288. package/src/memory/lifecycle-events-store.ts +63 -0
  289. package/src/memory/migrations/172-rename-created-by-session-id.ts +27 -0
  290. package/src/memory/migrations/173-rename-source-session-id.ts +16 -0
  291. package/src/memory/migrations/174-rename-thread-starters-table.ts +52 -0
  292. package/src/memory/migrations/175-create-lifecycle-events.ts +15 -0
  293. package/src/memory/migrations/176-drop-capability-card-state.ts +36 -0
  294. package/src/memory/migrations/177-create-trace-events-table.ts +40 -0
  295. package/src/memory/migrations/index.ts +6 -0
  296. package/src/memory/migrations/registry.ts +13 -0
  297. package/src/memory/retriever.test.ts +223 -96
  298. package/src/memory/retriever.ts +115 -138
  299. package/src/memory/schema/calls.ts +1 -1
  300. package/src/memory/schema/contacts.ts +1 -1
  301. package/src/memory/schema/infrastructure.ts +29 -0
  302. package/src/memory/schema/memory-core.ts +7 -17
  303. package/src/memory/schema/notifications.ts +1 -1
  304. package/src/memory/search/formatting.ts +23 -6
  305. package/src/memory/search/lexical.ts +2 -0
  306. package/src/memory/search/semantic.ts +2 -0
  307. package/src/memory/search/staleness.ts +5 -1
  308. package/src/memory/search/types.ts +4 -0
  309. package/src/memory/task-memory-cleanup.ts +96 -6
  310. package/src/memory/trace-event-store.ts +148 -0
  311. package/src/notifications/README.md +1 -1
  312. package/src/notifications/decision-engine.ts +45 -4
  313. package/src/notifications/emit-signal.ts +5 -4
  314. package/src/notifications/events-store.ts +4 -4
  315. package/src/notifications/signal.ts +1 -1
  316. package/src/oauth/manual-token-connection.ts +49 -25
  317. package/src/permissions/checker.ts +6 -5
  318. package/src/permissions/defaults.ts +4 -4
  319. package/src/prompts/__tests__/build-cli-reference-section.test.ts +9 -90
  320. package/src/prompts/cache-boundary.ts +8 -0
  321. package/src/prompts/system-prompt.ts +105 -634
  322. package/src/prompts/templates/BOOTSTRAP.md +172 -33
  323. package/src/prompts/templates/IDENTITY.md +8 -24
  324. package/src/prompts/templates/SOUL.md +20 -41
  325. package/src/prompts/templates/USER.md +3 -19
  326. package/src/prompts/user-reference.ts +14 -16
  327. package/src/providers/anthropic/client.ts +51 -19
  328. package/src/providers/gemini/client.ts +6 -9
  329. package/src/providers/managed-proxy/constants.ts +1 -7
  330. package/src/providers/managed-proxy/context.ts +0 -1
  331. package/src/providers/model-intents.ts +5 -5
  332. package/src/providers/openai/client.ts +10 -1
  333. package/src/providers/openrouter/client.ts +1 -0
  334. package/src/providers/ratelimit.ts +0 -35
  335. package/src/providers/registry.ts +3 -5
  336. package/src/providers/retry.ts +18 -1
  337. package/src/runtime/access-request-helper.ts +16 -2
  338. package/src/runtime/auth/route-policy.ts +7 -0
  339. package/src/runtime/channel-verification-service.ts +1 -1
  340. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  341. package/src/runtime/guardian-vellum-migration.ts +61 -1
  342. package/src/runtime/http-server.ts +8 -4
  343. package/src/runtime/migrations/vbundle-builder.ts +212 -32
  344. package/src/runtime/migrations/vbundle-import-analyzer.ts +74 -8
  345. package/src/runtime/migrations/vbundle-importer.ts +66 -1
  346. package/src/runtime/migrations/vbundle-validator.ts +17 -3
  347. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +4 -4
  348. package/src/runtime/routes/attachment-routes.ts +2 -2
  349. package/src/runtime/routes/btw-routes.ts +93 -0
  350. package/src/runtime/routes/channel-verification-routes.ts +19 -2
  351. package/src/runtime/routes/conversation-management-routes.ts +55 -1
  352. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-routes.ts +49 -5
  354. package/src/runtime/routes/conversation-starter-routes.ts +207 -0
  355. package/src/runtime/routes/guardian-bootstrap-routes.ts +13 -9
  356. package/src/runtime/routes/identity-intro-cache.ts +105 -0
  357. package/src/runtime/routes/identity-routes.ts +51 -0
  358. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -1
  359. package/src/runtime/routes/inbound-stages/verification-intercept.ts +1 -1
  360. package/src/runtime/routes/migration-routes.ts +25 -13
  361. package/src/runtime/routes/secret-routes.ts +18 -0
  362. package/src/runtime/routes/settings-routes.ts +9 -9
  363. package/src/runtime/routes/telemetry-routes.ts +53 -0
  364. package/src/runtime/routes/trace-event-routes.ts +62 -0
  365. package/src/runtime/tool-grant-request-helper.ts +1 -1
  366. package/src/runtime/verification-outbound-actions.ts +47 -31
  367. package/src/security/encrypted-store.ts +262 -78
  368. package/src/skills/catalog-install.ts +10 -0
  369. package/src/skills/managed-store.ts +2 -0
  370. package/src/skills/skill-memory.ts +222 -0
  371. package/src/subagent/manager.ts +1 -4
  372. package/src/telemetry/types.ts +10 -1
  373. package/src/telemetry/usage-telemetry-reporter.test.ts +7 -2
  374. package/src/telemetry/usage-telemetry-reporter.ts +53 -4
  375. package/src/tools/AGENTS.md +11 -11
  376. package/src/tools/acp/spawn.ts +1 -1
  377. package/src/tools/apps/executors.ts +8 -8
  378. package/src/tools/apps/registry.ts +1 -1
  379. package/src/tools/assets/materialize.ts +6 -6
  380. package/src/tools/assets/search.ts +10 -10
  381. package/src/tools/browser/__tests__/auth-cache.test.ts +2 -2
  382. package/src/tools/browser/__tests__/auth-detector.test.ts +4 -4
  383. package/src/tools/browser/auth-detector.ts +6 -6
  384. package/src/tools/browser/browser-execution.ts +13 -13
  385. package/src/tools/browser/browser-manager.ts +3 -3
  386. package/src/tools/browser/chrome-cdp.ts +5 -5
  387. package/src/tools/browser/jit-auth.ts +2 -2
  388. package/src/tools/browser/network-recorder.test.ts +2 -2
  389. package/src/tools/browser/network-recorder.ts +3 -3
  390. package/src/tools/browser/runtime-check.ts +3 -3
  391. package/src/tools/claude-code/claude-code.ts +2 -2
  392. package/src/tools/computer-use/definitions.ts +18 -18
  393. package/src/tools/credential-execution/make-authenticated-request.ts +4 -4
  394. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -3
  395. package/src/tools/credential-execution/run-authenticated-command.ts +4 -4
  396. package/src/tools/credentials/broker-types.ts +5 -5
  397. package/src/tools/credentials/broker.ts +15 -15
  398. package/src/tools/credentials/metadata-store.ts +2 -2
  399. package/src/tools/credentials/resolve.ts +1 -1
  400. package/src/tools/credentials/selection.ts +1 -1
  401. package/src/tools/credentials/tool-policy.ts +1 -1
  402. package/src/tools/credentials/vault.ts +115 -25
  403. package/src/tools/execution-target.ts +2 -2
  404. package/src/tools/executor.ts +7 -7
  405. package/src/tools/filesystem/edit.ts +2 -2
  406. package/src/tools/filesystem/read.ts +15 -4
  407. package/src/tools/filesystem/write.ts +1 -1
  408. package/src/tools/host-filesystem/edit.ts +2 -1
  409. package/src/tools/host-filesystem/read.ts +18 -1
  410. package/src/tools/host-filesystem/write.ts +1 -1
  411. package/src/tools/host-terminal/host-shell.ts +9 -8
  412. package/src/tools/mcp/mcp-tool-factory.ts +7 -6
  413. package/src/tools/memory/definitions.ts +6 -5
  414. package/src/tools/memory/handlers.test.ts +1 -1
  415. package/src/tools/network/__tests__/web-search.test.ts +3 -3
  416. package/src/tools/network/domain-normalize.ts +2 -2
  417. package/src/tools/network/script-proxy/session-manager.ts +10 -10
  418. package/src/tools/network/web-fetch.ts +1 -1
  419. package/src/tools/network/web-search.ts +3 -3
  420. package/src/tools/permission-checker.ts +8 -8
  421. package/src/tools/registry.ts +7 -7
  422. package/src/tools/schedule/list.ts +2 -2
  423. package/src/tools/schema-transforms.ts +31 -21
  424. package/src/tools/secret-detection-handler.ts +1 -1
  425. package/src/tools/sensitive-output-placeholders.ts +1 -1
  426. package/src/tools/shared/filesystem/edit-engine.ts +1 -1
  427. package/src/tools/shared/filesystem/file-ops-service.ts +3 -3
  428. package/src/tools/shared/filesystem/image-read.ts +25 -5
  429. package/src/tools/shared/filesystem/path-policy.ts +2 -2
  430. package/src/tools/shared/shell-output.ts +1 -1
  431. package/src/tools/side-effects.ts +1 -1
  432. package/src/tools/skills/execute.ts +1 -1
  433. package/src/tools/skills/load.ts +3 -3
  434. package/src/tools/skills/sandbox-runner.ts +3 -3
  435. package/src/tools/subagent/read.ts +1 -1
  436. package/src/tools/subagent/spawn.ts +2 -2
  437. package/src/tools/swarm/delegate.ts +3 -3
  438. package/src/tools/system/request-permission.ts +5 -4
  439. package/src/tools/terminal/backends/native.ts +4 -4
  440. package/src/tools/terminal/parser.ts +6 -6
  441. package/src/tools/terminal/sandbox-diagnostics.ts +1 -1
  442. package/src/tools/terminal/shell.ts +16 -16
  443. package/src/tools/tool-approval-handler.ts +21 -12
  444. package/src/tools/tool-manifest.ts +4 -4
  445. package/src/tools/types.ts +3 -3
  446. package/src/tools/ui-surface/definitions.ts +9 -37
  447. package/src/tools/watcher/list.ts +1 -1
  448. package/src/util/logger.ts +7 -2
  449. package/src/util/pricing.ts +4 -0
  450. package/src/util/retry.ts +29 -1
  451. package/src/workspace/migrations/007-web-search-provider-rename.ts +37 -0
  452. package/src/workspace/migrations/registry.ts +2 -0
  453. package/src/__tests__/cli-help-reference-sync.test.ts +0 -26
  454. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -190
  455. package/src/cli/reference.ts +0 -38
  456. package/src/memory/job-handlers/capability-cards.ts +0 -420
  457. package/src/runtime/routes/thread-starter-routes.ts +0 -294
package/ARCHITECTURE.md CHANGED
@@ -4,11 +4,11 @@ This document owns assistant-runtime architecture details. The repo-level archit
4
4
 
5
5
  ### Channel Onboarding Playbook Bootstrap
6
6
 
7
- - Transport metadata arrives via `session_create.transport` (HTTP) or `/channels/inbound` (`channelId`, optional `hints`, optional `uxBrief`).
7
+ - Transport metadata arrives via `conversation_create.transport` (HTTP) or `/channels/inbound` (`channelId`, optional `hints`, optional `uxBrief`).
8
8
  - Telegram webhook ingress injects deterministic channel-safe transport metadata (`hints` + `uxBrief`) so non-dashboard channels defer dashboard-only UI tasks cleanly.
9
9
  - `OnboardingPlaybookManager` resolves `<channel>_onboarding.md`, checks `onboarding/playbooks/registry.json`, and applies per-channel first-time fast-path onboarding.
10
10
  - `OnboardingOrchestrator` derives onboarding-mode guidance (post-hatch sequence, USER.md capture) from playbook + transport context.
11
- - Session runtime assembly injects both `<channel_onboarding_playbook>` and `<onboarding_mode>` context before provider calls, then strips both from persisted conversation history.
11
+ - Conversation runtime assembly injects both `<channel_onboarding_playbook>` and `<onboarding_mode>` context before provider calls, then strips both from persisted conversation history.
12
12
  - Permission setup remains user-initiated and hatch + first-conversation flows avoid proactive permission asks.
13
13
 
14
14
  ### Guardian Actor Context (Unified Across Channels)
@@ -17,7 +17,7 @@ This document owns assistant-runtime architecture details. The repo-level archit
17
17
  - The same resolver is used by:
18
18
  - `/channels/inbound` (Telegram/WhatsApp path) before run orchestration.
19
19
  - Inbound Twilio voice setup (`RelayConnection.handleSetup`) to seed call-time actor context.
20
- - Runtime channel runs pass this as `trustContext`, and session runtime assembly injects `<inbound_actor_context>` (via `inboundActorContextFromTrustContext()`) into provider-facing prompts.
20
+ - Runtime channel runs pass this as `trustContext`, and conversation runtime assembly injects `<inbound_actor_context>` (via `inboundActorContextFromTrustContext()`) into provider-facing prompts.
21
21
  - Voice calls mirror the same prompt contract: `CallController` receives guardian context on setup and refreshes it immediately after successful voice challenge verification, so the first post-verification turn is grounded as `actor_role: guardian`.
22
22
  - Voice-specific behavior (DTMF/speech verification flow, relay state machine) remains voice-local; only actor-role resolution is shared.
23
23
 
@@ -138,7 +138,7 @@ All guardian approval decisions — regardless of how they arrive — route thro
138
138
  | `src/runtime/routes/guardian-action-routes.ts` | HTTP route handlers for `GET /v1/guardian-actions/pending` and `POST /v1/guardian-actions/decision` |
139
139
  | `src/runtime/channel-approval-types.ts` | Channel-facing approval action types and `toApprovalActionOptions` bridge |
140
140
 
141
- ### Temporary Approval Modes (Session-Scoped Overrides)
141
+ ### Temporary Approval Modes (Conversation-Scoped Overrides)
142
142
 
143
143
  In addition to persistent trust rules (`always_allow` / `always_deny`), the approval system supports two **temporary** approval modes that auto-approve tool confirmations for the duration of a conversation or a fixed time window. These exist to reduce prompt fatigue during intensive sessions without permanently altering the trust configuration.
144
144
 
@@ -147,7 +147,7 @@ In addition to persistent trust rules (`always_allow` / `always_deny`), the appr
147
147
  1. **`allow_conversation`** — Auto-approve all tool confirmations for the remainder of the current conversation. The override persists until the session ends, the conversation is closed, or the mode is explicitly cleared.
148
148
  2. **`allow_10m`** — Auto-approve all tool confirmations for 10 minutes (configurable). The override expires lazily on the next read after the TTL elapses — no background sweep runs.
149
149
 
150
- **Session-scoped, in-memory only:** Overrides are keyed by `conversationId` and stored in an in-memory `Map` inside `conversation-approval-overrides.ts`. They do not survive daemon restarts, which is intentional — temporary approvals should not outlive the session that created them.
150
+ **Conversation-scoped, in-memory only:** Overrides are keyed by `conversationId` and stored in an in-memory `Map` inside `conversation-approval-overrides.ts`. They do not survive daemon restarts, which is intentional — temporary approvals should not outlive the conversation that created them.
151
151
 
152
152
  **Integration with the permission pipeline:** The permission checker (`src/tools/permission-checker.ts`) checks for an active temporary override via `getEffectiveMode()` before prompting the user. If an active override exists for the current conversation, the confirmation is auto-approved without surfacing a prompt. This check runs after persistent trust rules, so a persistent `deny` rule still takes precedence.
153
153
 
@@ -783,10 +783,10 @@ All client-server communication uses HTTP for request/response operations and Se
783
783
 
784
784
  The daemon emits two distinct error message types via SSE:
785
785
 
786
- | Message type | Scope | Purpose | Payload |
787
- | -------------------- | -------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
788
- | `conversation_error` | Session-scoped | Typed, actionable failures during chat/session runtime (e.g., provider network error, rate limit, API failure) | `sessionId`, `code` (typed enum), `userMessage`, `retryable`, `debugDetails?` |
789
- | `error` | Global | Generic, non-session failures (e.g., daemon startup errors, unknown message types) | `message` (string) |
786
+ | Message type | Scope | Purpose | Payload |
787
+ | -------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
788
+ | `conversation_error` | Conversation-scoped | Typed, actionable failures during conversation runtime (e.g., provider network error, rate limit, API failure) | `sessionId`, `code` (typed enum), `userMessage`, `retryable`, `debugDetails?` |
789
+ | `error` | Global | Generic, non-session failures (e.g., daemon startup errors, unknown message types) | `message` (string) |
790
790
 
791
791
  **Design rationale:** `conversation_error` carries structured metadata (error code, retryable flag, debug details) so the client can present actionable UI — a toast with retry/dismiss buttons — rather than a generic error banner. The older `error` type is retained for backward compatibility with non-session contexts.
792
792
 
@@ -845,7 +845,7 @@ sequenceDiagram
845
845
  end
846
846
  ```
847
847
 
848
- 1. **Daemon** encounters a session-scoped failure, classifies it via `classifyConversationError()`, and sends a `conversation_error` SSE event with the session ID, typed error code, user-facing message, retryable flag, and optional debug details. Session-scoped failures emit _only_ `conversation_error` (never the generic `error` type) to prevent cross-session bleed.
848
+ 1. **Daemon** encounters a conversation-scoped failure, classifies it via `classifyConversationError()`, and sends a `conversation_error` SSE event with the conversation ID, typed error code, user-facing message, retryable flag, and optional debug details. Conversation-scoped failures emit _only_ `conversation_error` (never the generic `error` type) to prevent cross-conversation bleed.
849
849
  2. **ChatViewModel** receives the error via DaemonClient's `subscribe()` stream (each view model gets an independent stream), sets the `conversationError` property, and transitions out of the streaming/loading state so the UI is interactive. If the error arrives during an active cancel (`wasCancelling == true`), it is suppressed — cancel only shows `generation_cancelled` behavior.
850
850
  3. **ChatView** observes the published `conversationError` and displays an actionable toast with a category-specific icon and accent color:
851
851
  - **Retry** (shown when `retryable` is true): calls `retryAfterConversationError()`, which clears the error and sends a `regenerate` message to the daemon.
package/Dockerfile CHANGED
@@ -89,6 +89,9 @@ RUN echo 'Dir::State "/data/dpkg";' > /etc/apt/apt.conf.d/99data-dir && \
89
89
  ENV PATH="/data/usr/bin:/data/usr/sbin:${PATH}"
90
90
  ENV LD_LIBRARY_PATH="/data/usr/lib:/data/usr/lib/x86_64-linux-gnu:/data/usr/lib/aarch64-linux-gnu"
91
91
 
92
+ # Ensure the CES bootstrap socket volume is writable by the non-root CES user.
93
+ RUN mkdir -p /run/ces-bootstrap && chmod 777 /run/ces-bootstrap
94
+
92
95
  USER root
93
96
 
94
97
  EXPOSE 3001
package/README.md CHANGED
@@ -77,17 +77,17 @@ bun run src/index.ts # interactive CLI session
77
77
 
78
78
  ### CLI commands
79
79
 
80
- | Command | Description |
81
- | --------------------------------------------- | ------------------------------------------------ |
82
- | `vellum wake` | Start assistant + gateway from current checkout |
83
- | `vellum sleep` | Stop assistant + gateway processes |
84
- | `vellum ps` | List assistants and per-assistant process status |
85
- | `assistant` | Launch interactive CLI session |
86
- | `assistant sessions list\|new\|export\|clear` | Manage conversation sessions |
87
- | `assistant config set\|get\|list` | Manage configuration |
88
- | `assistant keys set\|list\|delete` | Manage API keys in secure storage |
89
- | `assistant trust list\|remove\|clear` | Manage trust rules |
90
- | `assistant doctor` | Run diagnostic checks |
80
+ | Command | Description |
81
+ | -------------------------------------------------- | ------------------------------------------------ |
82
+ | `vellum wake` | Start assistant + gateway from current checkout |
83
+ | `vellum sleep` | Stop assistant + gateway processes |
84
+ | `vellum ps` | List assistants and per-assistant process status |
85
+ | `assistant` | Launch interactive CLI session |
86
+ | `assistant conversations list\|new\|export\|clear` | Manage conversations |
87
+ | `assistant config set\|get\|list` | Manage configuration |
88
+ | `assistant keys set\|list\|delete` | Manage API keys in secure storage |
89
+ | `assistant trust list\|remove\|clear` | Manage trust rules |
90
+ | `assistant doctor` | Run diagnostic checks |
91
91
 
92
92
  ## Project Structure
93
93
 
@@ -542,7 +542,7 @@ sequenceDiagram
542
542
  Materialize->>DB: load attachment (including base64 data)
543
543
  Materialize->>Visibility: isAttachmentVisible(attachmentCtx, currentCtx)
544
544
  Note over Visibility: Second visibility check at materialize time<br/>prevents TOCTOU between search and materialize
545
- Materialize->>Materialize: size check (max 50 MB)
545
+ Materialize->>Materialize: size check (max 100 MB)
546
546
  Materialize->>Sandbox: write decoded bytes to destination
547
547
  Materialize-->>Model: "Materialized 'photo.jpg' to /workspace/media/photo.jpg"
548
548
  ```
@@ -586,7 +586,7 @@ graph TB
586
586
  ### Materialize Safeguards
587
587
 
588
588
  - **Sandbox path enforcement**: Destination path must resolve inside the sandbox working directory
589
- - **Size limit**: 50 MB ceiling prevents materializing excessively large attachments
589
+ - **Size limit**: 100 MB ceiling prevents materializing excessively large attachments
590
590
  - **Double visibility check**: Both `asset_search` and `asset_materialize` independently verify visibility, preventing TOCTOU races between search and use
591
591
  - **Risk level**: Both tools are `RiskLevel.Low` since they read existing data and write only within the sandbox
592
592
 
@@ -250,7 +250,7 @@ The recall pipeline runs on every turn that passes the `needsMemory` gate (skips
250
250
  9. **Two-layer XML injection** (`formatting.ts`): Budget-aware rendering into four XML sections:
251
251
 
252
252
  ```xml
253
- <memory_context>
253
+ <memory_context __injected>
254
254
 
255
255
  <user_identity>
256
256
  <!-- identity-kind tier 1 items (plain statements) -->
@@ -273,7 +273,7 @@ The recall pipeline runs on every turn that passes the `needsMemory` gate (skips
273
273
 
274
274
  Empty sections are omitted. Each section has a per-item token budget (150 tokens for tier 1, 100 for tier 2). Tier 1 sections consume budget first; tier 2 uses the remainder.
275
275
 
276
- 10. **Injection strategy**: The rendered `<memory_context>` block is injected as a separate user + assistant acknowledgment message pair before the last user message (`injectMemoryRecallAsSeparateMessage`). This separates memory context from the user's actual query.
276
+ 10. **Injection strategy**: The rendered `<memory_context __injected>` block is prepended as a text content block to the last user message (`injectMemoryRecallAsUserBlock`), following the same pattern as workspace, temporal, and other runtime injections. Stripping is handled by the generic `stripUserTextBlocksByPrefix` mechanism matching the `<memory_context __injected>` prefix (with a backward-compat entry for the legacy `<memory_context>` prefix from older history). This avoids synthetic message pairs and preserves prompt prefix caching between turns.
277
277
 
278
278
  ### Internal-Only Trust Gating
279
279
 
@@ -464,7 +464,7 @@ The Anthropic provider places `cache_control: { type: 'ephemeral' }` on the **la
464
464
 
465
465
  ## Temporal Context Injection — Date Grounding
466
466
 
467
- The session injects a `<temporal_context>` block into every user message at runtime, giving the model awareness of the current date, current local time, current UTC time, timezone source metadata, upcoming weekend/work week windows, and a 14-day horizon of labelled future dates. This enables reliable reasoning about future dates (e.g. "plan a trip for next weekend") without persisting volatile temporal data in conversation history.
467
+ The session injects a `<temporal_context>` block into every user message at runtime, giving the model awareness of the current date, current local time, current UTC time, and timezone source metadata. This enables reliable reasoning about dates and times without persisting volatile temporal data in conversation history.
468
468
 
469
469
  ### Per-turn flow
470
470
 
@@ -488,7 +488,6 @@ graph TB
488
488
  - **Clock source invariant**: Absolute time (`now`) always comes from the assistant host clock (`Date.now()`), never from channel/client clocks.
489
489
  - **Timezone precedence**: If `ui.userTimezone` is configured, temporal context uses it for local-date interpretation. Otherwise it falls back to memory-stored timezone, then assistant host timezone.
490
490
  - **Timezone-aware**: Uses `Intl.DateTimeFormat` APIs for DST-safe date arithmetic and timezone validation/canonicalization.
491
- - **Bounded output**: Hard-capped at 1500 characters and 14 horizon entries to prevent prompt bloat.
492
491
  - **Runtime-only**: The injected `<temporal_context>` block is stripped from `this.messages` after the agent loop completes via `stripTemporalContext`. It never persists in conversation history.
493
492
  - **Specific strip prefix**: The strip function matches the exact injected prefix (`<temporal_context>\nToday:`) to avoid accidentally removing user-authored text that starts with `<temporal_context>`.
494
493
  - **Retry paths**: Temporal context is included in all three `applyRuntimeInjections` call sites (main path, compact retry, media-trim retry).
@@ -60,36 +60,29 @@ The existing `host_bash` tool executes commands on the host machine without any
60
60
 
61
61
  **Implication**: `host_bash` represents a weaker security tier. Agents that require the strong secrecy guarantee must use `run_authenticated_command` instead. Trust rules and permission policies should reflect this distinction — managed deployments may deny `host_bash` entirely for untrusted agents while allowing `run_authenticated_command`.
62
62
 
63
- ### 2. Local static secrets are local-mode only — by design
63
+ ### 2. Local static secrets are local-mode only — by policy
64
64
 
65
- For the initial implementation, local static secrets (API keys, tokens stored via the credential store in `~/.vellum/protected/`) are only accessible to CES in **local mode**, where CES runs as a child process of the assistant as the same OS user. CES reads them at materialization time via direct filesystem access.
65
+ For the current implementation, local static secrets (API keys, tokens stored via the credential store in `~/.vellum/protected/`) are only accessible to CES in **local mode**, where CES runs as a child process of the assistant. CES reads them at materialization time via direct filesystem access.
66
66
 
67
- In **managed mode**, `local_static` handles are not supported and the CES returns a clear error for any `local_static` handle. Managed deployments use `platform_oauth` handles exclusively. This is a deliberate architectural decision, not a temporary limitation.
67
+ In **managed mode**, `local_static` handles are not supported and the CES returns a clear error for any `local_static` handle. Managed deployments use `platform_oauth` handles exclusively. With v2 `store.key`, this is a **policy choice** (simpler lifecycle, centralized token management) rather than a technical limitation — the UID-independent key file could be shared via volume mount.
68
68
 
69
- #### Why `local_static` cannot work in managed mode
69
+ #### Historical: v1 key derivation blocker (resolved in v2)
70
70
 
71
- The original design considered having managed deployments share static secrets via the assistant data volume. This is technically impossible due to how the encrypted key store works.
71
+ The v1 encrypted key store uses PBKDF2 key derivation where the encryption key is derived from `userInfo().username` and `userInfo().homedir`. In managed deployments the assistant and CES sidecar run as different OS users, producing different derived keys — making it impossible for CES to decrypt secrets stored by the assistant.
72
72
 
73
- The `local-secure-key-backend.ts` module uses PBKDF2 key derivation where the encryption key is derived from `userInfo().username` and `userInfo().homedir`. In managed deployments:
73
+ v2 stores replaced PBKDF2 derivation with a random 32-byte key stored at `<vellumRoot>/protected/store.key`. This key is UID-independent and can be shared via volume mount, removing the technical barrier to `local_static` in managed mode.
74
74
 
75
- - The **assistant container** runs as `root` (homedir `/root`)
76
- - The **CES sidecar container** runs as `ces` / uid 1001 (homedir `/home/ces`)
75
+ The policy decision to use `platform_oauth` exclusively in managed mode still stands for operational reasons: simpler credential lifecycle, centralized token management, and no need to synchronize key files across containers. Future iterations may enable `local_static` in managed mode via shared `store.key` volume mounts if there is a compelling use case.
77
76
 
78
- These produce different PBKDF2-derived AES keys. Even if the encrypted key store file (`keys.enc`) were mounted as a shared volume, CES would derive a different decryption key and silently fail to decrypt the secrets.
77
+ #### Rejected alternatives (v1-era, historical context)
79
78
 
80
- #### Rejected alternatives
79
+ These alternatives were evaluated for the v1 key store and rejected. They are retained for historical context — the v2 `store.key` format resolves the underlying issue without hitting these trade-offs.
81
80
 
82
- Three alternatives were evaluated and rejected because each breaks a core security invariant:
81
+ 1. **Mount decrypted secrets into the CES container** Breaks the "secrets never in assistant process memory" boundary (Boundary Invariant #2).
83
82
 
84
- 1. **Mount decrypted secrets into the CES container** — This would require decrypting secrets in the assistant container and writing plaintext to a shared volume, breaking the "secrets never in assistant process memory" boundary (Boundary Invariant #2).
83
+ 2. **Use shared key derivation independent of UID** — Was rejected for v1 because it weakened the encrypted-at-rest model. The v2 `store.key` approach achieves UID-independent decryption without the per-user identity trade-off, since the random key file is protected by filesystem permissions rather than derivation entropy.
85
84
 
86
- 2. **Use shared key derivation independent of UID** — Deriving the encryption key from a shared secret (e.g., a pod-level token) rather than per-user identity would weaken the encrypted-at-rest security model. The UID-based derivation ensures that only the user who stored the credential can decrypt it, which is a fundamental property of the local credential store.
87
-
88
- 3. **Pre-decrypt and pass via the RPC socket** — Having the assistant decrypt the secret and send it to CES over the Unix socket would mean the assistant process handles plaintext credential values, directly violating the CES process-boundary isolation guarantee.
89
-
90
- Since all alternatives break security invariants that CES exists to enforce, managed deployments route credential access through `platform_oauth` where the platform manages token lifecycle and CES requests materialized tokens via the platform proxy endpoint.
91
-
92
- Future iterations may move secret storage to a dedicated secret manager (e.g., cloud KMS, Vault) with CES as the only authorized reader, which would enable static secrets in managed mode without compromising the process-boundary isolation.
85
+ 3. **Pre-decrypt and pass via the RPC socket** — Violates the CES process-boundary isolation guarantee.
93
86
 
94
87
  ### 3. Platform OAuth materialization stays on the platform
95
88
 
@@ -396,7 +389,7 @@ This means the helper is subject to the same cooperative egress limitation as th
396
389
 
397
390
  The following capabilities are intentionally deferred beyond v1:
398
391
 
399
- - **`local_static` handles in managed mode** — Structurally unsupported due to PBKDF2 key derivation depending on per-container UID (see Locked Decision #2 for full rationale and rejected alternatives). Managed mode returns a clear error and requires `platform_oauth` handles exclusively.
392
+ - **`local_static` handles in managed mode** — Technically feasible with v2 `store.key` (UID-independent), but managed mode currently uses `platform_oauth` exclusively as a policy choice (see Locked Decision #2). May be enabled in the future via shared `store.key` volume mount if there is a compelling use case.
400
393
  - **Cloud KMS/Vault integration for secret storage** — v1 reads secrets from filesystem (`~/.vellum/protected/` locally, `/ces-data` in managed). Moving to a dedicated secrets manager is a future enhancement.
401
394
  - **Multi-CES-instance support** — Each assistant pod runs exactly one CES sidecar. Horizontal scaling of CES within a pod is not supported.
402
395
  - **Cross-pod credential sharing** — CES grants are scoped to a single pod. There is no grant federation across pods or assistant instances.
@@ -15,10 +15,11 @@ export type RpcError = z.infer<typeof RpcErrorSchema>;
15
15
 
16
16
  /**
17
17
  * Error returned when a local_static credential handle is used in managed
18
- * mode. The encrypted key store uses PBKDF2 key derivation from user
19
- * identity (username, homedir), but the assistant container runs as root
20
- * while CES runs as ces different derived keys make decryption silently
21
- * fail. Managed deployments must use platform_oauth handles exclusively.
18
+ * mode. v2 stores use a UID-independent `store.key` file that removes the
19
+ * technical barrier (legacy v1 relied on PBKDF2 key derivation from user
20
+ * identity, which broke across container users). The restriction is now a
21
+ * policy choice: managed deployments use platform_oauth handles exclusively
22
+ * for simpler lifecycle and centralized token management.
22
23
  */
23
24
  export const MANAGED_LOCAL_STATIC_REJECTION_ERROR =
24
25
  "local_static credential handles are not supported in managed mode. " +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.4.56",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -93,8 +93,8 @@ function mockServer(address: string): ServerWithRequestIP {
93
93
  /** Mock loopback server -- returns 127.0.0.1 for all requests. */
94
94
  const loopbackServer = mockServer("127.0.0.1");
95
95
 
96
- /** Mock non-loopback server -- returns a LAN IP for all requests. */
97
- const nonLoopbackServer = mockServer("192.168.1.50");
96
+ /** Mock non-loopback server -- returns a public IP for all requests. */
97
+ const nonLoopbackServer = mockServer("203.0.113.50");
98
98
 
99
99
  initializeDb();
100
100
 
@@ -676,11 +676,11 @@ describe("pairing credential flow", () => {
676
676
  });
677
677
 
678
678
  // ---------------------------------------------------------------------------
679
- // Bootstrap loopback guard tests
679
+ // Bootstrap private-network guard tests
680
680
  // ---------------------------------------------------------------------------
681
681
 
682
- describe("bootstrap loopback guard", () => {
683
- test("rejects bootstrap request with X-Forwarded-For header", async () => {
682
+ describe("bootstrap private-network guard", () => {
683
+ test("rejects bootstrap request with public X-Forwarded-For", async () => {
684
684
  const { handleGuardianBootstrap } =
685
685
  await import("../runtime/routes/guardian-bootstrap-routes.js");
686
686
 
@@ -688,7 +688,7 @@ describe("bootstrap loopback guard", () => {
688
688
  method: "POST",
689
689
  headers: {
690
690
  "Content-Type": "application/json",
691
- "X-Forwarded-For": "10.0.0.1",
691
+ "X-Forwarded-For": "203.0.113.1",
692
692
  },
693
693
  body: JSON.stringify({ platform: "macos", deviceId: "test-device" }),
694
694
  });
@@ -699,7 +699,7 @@ describe("bootstrap loopback guard", () => {
699
699
  expect(body.error.message).toContain("local-only");
700
700
  });
701
701
 
702
- test("rejects bootstrap request from non-loopback IP", async () => {
702
+ test("rejects bootstrap request from public IP peer", async () => {
703
703
  const { handleGuardianBootstrap } =
704
704
  await import("../runtime/routes/guardian-bootstrap-routes.js");
705
705
 
@@ -62,6 +62,7 @@ mock.module("@anthropic-ai/sdk", () => ({
62
62
  }));
63
63
 
64
64
  // Import after mocking
65
+ import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "../prompts/system-prompt.js";
65
66
  import {
66
67
  AnthropicProvider,
67
68
  PLACEHOLDER_BLOCKS_OMITTED,
@@ -155,6 +156,25 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
155
156
  expect(lastStreamParams!.system).toBeUndefined();
156
157
  });
157
158
 
159
+ test("splits system prompt into two cache blocks on boundary marker", async () => {
160
+ const staticBlock = "You are a helpful assistant.";
161
+ const dynamicBlock = "User workspace files here.";
162
+ const prompt = staticBlock + SYSTEM_PROMPT_CACHE_BOUNDARY + dynamicBlock;
163
+
164
+ await provider.sendMessage([userMsg("Hi")], undefined, prompt);
165
+
166
+ const system = lastStreamParams!.system as Array<{
167
+ type: string;
168
+ text: string;
169
+ cache_control?: { type: string };
170
+ }>;
171
+ expect(system).toHaveLength(2);
172
+ expect(system[0].text).toBe(staticBlock);
173
+ expect(system[0].cache_control).toEqual({ type: "ephemeral" });
174
+ expect(system[1].text).toBe(dynamicBlock);
175
+ expect(system[1].cache_control).toEqual({ type: "ephemeral" });
176
+ });
177
+
158
178
  // -----------------------------------------------------------------------
159
179
  // Tool cache control
160
180
  // -----------------------------------------------------------------------
@@ -1307,6 +1327,158 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
1307
1327
  expect(userMsgs[2].content[0].cache_control).toBeUndefined();
1308
1328
  expect(userMsgs[2].content[1].cache_control).toBeUndefined();
1309
1329
  });
1330
+
1331
+ // -----------------------------------------------------------------------
1332
+ // is_error + contentBlocks — non-text blocks must be stripped
1333
+ // -----------------------------------------------------------------------
1334
+
1335
+ test("is_error tool_result strips non-text contentBlocks (images)", async () => {
1336
+ const messages: Message[] = [
1337
+ userMsg("Do something"),
1338
+ toolUseMsg("tu_img", "file_read"),
1339
+ {
1340
+ role: "user",
1341
+ content: [
1342
+ {
1343
+ type: "tool_result",
1344
+ tool_use_id: "tu_img",
1345
+ content: "Error: file not found",
1346
+ is_error: true,
1347
+ contentBlocks: [
1348
+ {
1349
+ type: "image",
1350
+ source: {
1351
+ type: "base64",
1352
+ media_type: "image/png",
1353
+ data: "iVBOR",
1354
+ },
1355
+ },
1356
+ { type: "text", text: "extra error detail" },
1357
+ ],
1358
+ },
1359
+ ],
1360
+ },
1361
+ ];
1362
+ await provider.sendMessage(messages);
1363
+
1364
+ const sent = lastStreamParams!.messages as Array<{
1365
+ role: string;
1366
+ content: Array<{
1367
+ type: string;
1368
+ tool_use_id?: string;
1369
+ is_error?: boolean;
1370
+ content?: unknown;
1371
+ }>;
1372
+ }>;
1373
+
1374
+ const toolResult = sent[2].content.find(
1375
+ (b) => b.type === "tool_result" && b.tool_use_id === "tu_img",
1376
+ )!;
1377
+ expect(toolResult.is_error).toBe(true);
1378
+
1379
+ // Content should be an array with only text blocks (no images)
1380
+ const parts = toolResult.content as Array<{ type: string }>;
1381
+ expect(Array.isArray(parts)).toBe(true);
1382
+ expect(parts.every((p) => p.type === "text")).toBe(true);
1383
+ // Original text + the extra text contentBlock
1384
+ expect(parts).toHaveLength(2);
1385
+ });
1386
+
1387
+ test("is_error tool_result with only image contentBlocks falls back to text-only", async () => {
1388
+ const messages: Message[] = [
1389
+ userMsg("Do something"),
1390
+ toolUseMsg("tu_img2", "file_read"),
1391
+ {
1392
+ role: "user",
1393
+ content: [
1394
+ {
1395
+ type: "tool_result",
1396
+ tool_use_id: "tu_img2",
1397
+ content: "Error: file not found",
1398
+ is_error: true,
1399
+ contentBlocks: [
1400
+ {
1401
+ type: "image",
1402
+ source: {
1403
+ type: "base64",
1404
+ media_type: "image/png",
1405
+ data: "iVBOR",
1406
+ },
1407
+ },
1408
+ ],
1409
+ },
1410
+ ],
1411
+ },
1412
+ ];
1413
+ await provider.sendMessage(messages);
1414
+
1415
+ const sent = lastStreamParams!.messages as Array<{
1416
+ role: string;
1417
+ content: Array<{
1418
+ type: string;
1419
+ tool_use_id?: string;
1420
+ is_error?: boolean;
1421
+ content?: unknown;
1422
+ }>;
1423
+ }>;
1424
+
1425
+ const toolResult = sent[2].content.find(
1426
+ (b) => b.type === "tool_result" && b.tool_use_id === "tu_img2",
1427
+ )!;
1428
+ expect(toolResult.is_error).toBe(true);
1429
+
1430
+ // All images stripped → no usable blocks → falls back to text-only content
1431
+ expect(toolResult.content).toBe("Error: file not found");
1432
+ });
1433
+
1434
+ test("non-error tool_result preserves image contentBlocks", async () => {
1435
+ const messages: Message[] = [
1436
+ userMsg("Do something"),
1437
+ toolUseMsg("tu_img3", "file_read"),
1438
+ {
1439
+ role: "user",
1440
+ content: [
1441
+ {
1442
+ type: "tool_result",
1443
+ tool_use_id: "tu_img3",
1444
+ content: "Success",
1445
+ is_error: false,
1446
+ contentBlocks: [
1447
+ {
1448
+ type: "image",
1449
+ source: {
1450
+ type: "base64",
1451
+ media_type: "image/png",
1452
+ data: "iVBOR",
1453
+ },
1454
+ },
1455
+ ],
1456
+ },
1457
+ ],
1458
+ },
1459
+ ];
1460
+ await provider.sendMessage(messages);
1461
+
1462
+ const sent = lastStreamParams!.messages as Array<{
1463
+ role: string;
1464
+ content: Array<{
1465
+ type: string;
1466
+ tool_use_id?: string;
1467
+ is_error?: boolean;
1468
+ content?: unknown;
1469
+ }>;
1470
+ }>;
1471
+
1472
+ const toolResult = sent[2].content.find(
1473
+ (b) => b.type === "tool_result" && b.tool_use_id === "tu_img3",
1474
+ )!;
1475
+ expect(toolResult.is_error).toBe(false);
1476
+
1477
+ // Non-error: images should be preserved in the content array
1478
+ const parts = toolResult.content as Array<{ type: string }>;
1479
+ expect(Array.isArray(parts)).toBe(true);
1480
+ expect(parts.some((p) => p.type === "image")).toBe(true);
1481
+ });
1310
1482
  });
1311
1483
 
1312
1484
  // ---------------------------------------------------------------------------
@@ -63,7 +63,21 @@ function makeContext(overrides: Partial<ToolContext> = {}): ToolContext {
63
63
 
64
64
  const mockStore = makeMockStore();
65
65
 
66
- mock.module("../memory/app-store.js", () => mockStore);
66
+ mock.module("../memory/app-store.js", () => ({
67
+ ...mockStore,
68
+ getAppsDir: () => "/tmp/test-apps",
69
+ isMultifileApp: (app: AppDefinition) => app.formatVersion === 2,
70
+ }));
71
+
72
+ // Mock compileApp for multifile scaffold path
73
+ mock.module("../bundler/app-compiler.js", () => ({
74
+ compileApp: async () => ({
75
+ ok: true,
76
+ errors: [],
77
+ warnings: [],
78
+ durationMs: 0,
79
+ }),
80
+ }));
67
81
 
68
82
  // ---------------------------------------------------------------------------
69
83
  // Import skill scripts (after mocking)
@@ -76,7 +76,7 @@ mock.module("../config/loader.js", () => ({
76
76
  summaryModel: "mock-model",
77
77
  maxSummaryTokens: 512,
78
78
  },
79
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
79
+ rateLimit: { maxRequestsPerMinute: 0 },
80
80
  timeouts: { permissionTimeoutSec: 300 },
81
81
  skills: { entries: {}, allowBundled: true },
82
82
  permissions: { mode: "workspace" },
@@ -166,7 +166,7 @@ mock.module("../memory/retriever.js", () => ({
166
166
  injectedTokens: 0,
167
167
  latencyMs: 0,
168
168
  }),
169
- stripMemoryRecallMessages: (msgs: Message[]) => msgs,
169
+ injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
170
170
  }));
171
171
 
172
172
  mock.module("../context/window-manager.js", () => ({
@@ -42,9 +42,8 @@ mock.module("../config/loader.js", () => ({
42
42
  model: "test",
43
43
  provider: "test",
44
44
  memory: { enabled: false },
45
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
45
+ rateLimit: { maxRequestsPerMinute: 0 },
46
46
  secretDetection: { enabled: false },
47
- sandbox: { enabled: false },
48
47
  contextWindow: { maxInputTokens: 200000 },
49
48
  services: {
50
49
  inference: {
@@ -55,9 +54,9 @@ mock.module("../config/loader.js", () => ({
55
54
  "image-generation": {
56
55
  mode: "your-own",
57
56
  provider: "gemini",
58
- model: "gemini-2.5-flash-image",
57
+ model: "gemini-3.1-flash-image-preview",
59
58
  },
60
- "web-search": { mode: "your-own", provider: "anthropic-native" },
59
+ "web-search": { mode: "your-own", provider: "inference-provider-native" },
61
60
  },
62
61
  }),
63
62
  }));
@@ -34,7 +34,7 @@ mock.module("../config/loader.js", () => ({
34
34
  model: "test",
35
35
  provider: "test",
36
36
  memory: { enabled: false },
37
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
37
+ rateLimit: { maxRequestsPerMinute: 0 },
38
38
  }),
39
39
  }));
40
40
 
@@ -263,7 +263,7 @@ describe("AssetMaterializeTool materialization", () => {
263
263
  describe("AssetMaterializeTool size limit", () => {
264
264
  beforeEach(resetTables);
265
265
 
266
- test("rejects attachment exceeding 50MB limit", async () => {
266
+ test("rejects attachment exceeding 100MB limit", async () => {
267
267
  // Simulate a large attachment by inserting directly into the DB
268
268
  // with a sizeBytes value over the limit
269
269
  const db = getDb();
@@ -271,7 +271,7 @@ describe("AssetMaterializeTool size limit", () => {
271
271
  db.run(
272
272
  `INSERT INTO attachments (id, original_filename, mime_type, size_bytes, kind, data_base64, created_at)
273
273
  VALUES ('${fakeId}', 'huge.bin', 'application/octet-stream', ${
274
- 51 * 1024 * 1024
274
+ 101 * 1024 * 1024
275
275
  }, 'document', 'AAAA', ${Date.now()})`,
276
276
  );
277
277
 
@@ -314,8 +314,8 @@ describe("AssetMaterializeTool metadata", () => {
314
314
  ).toHaveProperty("destination_path");
315
315
  });
316
316
 
317
- test("tool has MEDIUM risk level", () => {
318
- expect(assetMaterializeTool.defaultRiskLevel).toBe(RiskLevel.Medium);
317
+ test("tool has LOW risk level", () => {
318
+ expect(assetMaterializeTool.defaultRiskLevel).toBe(RiskLevel.Low);
319
319
  });
320
320
 
321
321
  test("tool category is assets", () => {
@@ -33,7 +33,7 @@ mock.module("../config/loader.js", () => ({
33
33
  model: "test",
34
34
  provider: "test",
35
35
  memory: { enabled: false },
36
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
36
+ rateLimit: { maxRequestsPerMinute: 0 },
37
37
  }),
38
38
  }));
39
39