@vellumai/assistant 0.6.1 → 0.6.3

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 (463) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docker-entrypoint.sh +12 -2
  4. package/docs/architecture/memory.md +1 -1
  5. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  6. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  7. package/openapi.yaml +184 -69
  8. package/package.json +41 -41
  9. package/scripts/generate-openapi.ts +1 -2
  10. package/src/__tests__/acp-session.test.ts +43 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +1 -0
  13. package/src/__tests__/app-source-watcher.test.ts +37 -11
  14. package/src/__tests__/approval-routes-http.test.ts +178 -1
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  17. package/src/__tests__/browser-manager.test.ts +40 -27
  18. package/src/__tests__/catalog-files.test.ts +862 -0
  19. package/src/__tests__/channel-approvals.test.ts +53 -0
  20. package/src/__tests__/checker.test.ts +104 -170
  21. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  22. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  23. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  24. package/src/__tests__/config-schema.test.ts +125 -48
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  26. package/src/__tests__/context-overflow-approval.test.ts +21 -6
  27. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  28. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  29. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  30. package/src/__tests__/conversation-attachments.test.ts +80 -4
  31. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  32. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  33. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  34. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  35. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  36. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  37. package/src/__tests__/conversation-queue.test.ts +45 -2
  38. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  39. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  40. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  41. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  42. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  43. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  44. package/src/__tests__/conversation-store.test.ts +195 -0
  45. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  46. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -3
  47. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  48. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  49. package/src/__tests__/credential-vault.test.ts +152 -13
  50. package/src/__tests__/credentials-cli.test.ts +2 -2
  51. package/src/__tests__/date-context.test.ts +4 -4
  52. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  53. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  54. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  55. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  56. package/src/__tests__/gemini-provider.test.ts +2 -2
  57. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  58. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  59. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  60. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  61. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  62. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  63. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  64. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  65. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  66. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  67. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  68. package/src/__tests__/host-browser-routes.test.ts +198 -0
  69. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  70. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  71. package/src/__tests__/host-file-proxy.test.ts +185 -1
  72. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  73. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  74. package/src/__tests__/host-shell-tool.test.ts +1 -11
  75. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  76. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  77. package/src/__tests__/inline-command-runner.test.ts +7 -5
  78. package/src/__tests__/integration-status.test.ts +6 -7
  79. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  80. package/src/__tests__/log-export-workspace.test.ts +190 -0
  81. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  82. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  83. package/src/__tests__/mcp-health-check.test.ts +10 -3
  84. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  85. package/src/__tests__/migration-export-http.test.ts +61 -2
  86. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  87. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  88. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  89. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  90. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  91. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  92. package/src/__tests__/oauth-cli.test.ts +707 -60
  93. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  94. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  95. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  96. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  97. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  98. package/src/__tests__/oauth-store.test.ts +1386 -182
  99. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  100. package/src/__tests__/onboarding-template-contract.test.ts +74 -55
  101. package/src/__tests__/openai-provider.test.ts +2 -2
  102. package/src/__tests__/outlook-categories.test.ts +1 -1
  103. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  104. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  105. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  106. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  107. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  108. package/src/__tests__/outlook-trash.test.ts +1 -1
  109. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  110. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  111. package/src/__tests__/permission-mode.test.ts +28 -56
  112. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  113. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  114. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  115. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  116. package/src/__tests__/require-fresh-approval.test.ts +40 -3
  117. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  118. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  119. package/src/__tests__/schedule-routes.test.ts +162 -0
  120. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  121. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  123. package/src/__tests__/set-permission-mode.test.ts +13 -250
  124. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  125. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  126. package/src/__tests__/slack-channel-config.test.ts +12 -15
  127. package/src/__tests__/subagent-detail.test.ts +44 -2
  128. package/src/__tests__/subagent-disposal.test.ts +1 -0
  129. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  130. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  131. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  132. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  134. package/src/__tests__/subagent-tools.test.ts +1 -0
  135. package/src/__tests__/subagent-types.test.ts +1 -0
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  137. package/src/__tests__/system-prompt.test.ts +72 -1
  138. package/src/__tests__/task-scheduler.test.ts +32 -6
  139. package/src/__tests__/telegram-config.test.ts +10 -13
  140. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  141. package/src/__tests__/terminal-tools.test.ts +11 -5
  142. package/src/__tests__/test-preload.ts +14 -0
  143. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  144. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  145. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  146. package/src/__tests__/tool-executor.test.ts +0 -1
  147. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  148. package/src/__tests__/top-level-renderer.test.ts +73 -1
  149. package/src/__tests__/transport-hints-queue.test.ts +62 -0
  150. package/src/__tests__/trust-store.test.ts +4 -4
  151. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  152. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  153. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  154. package/src/__tests__/workspace-policy.test.ts +2 -7
  155. package/src/acp/client-handler.ts +30 -4
  156. package/src/agent/loop.ts +12 -35
  157. package/src/approvals/guardian-request-resolvers.ts +21 -15
  158. package/src/browser-session/__tests__/manager.test.ts +297 -0
  159. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  160. package/src/browser-session/backends/extension.ts +26 -0
  161. package/src/browser-session/backends/local.ts +24 -0
  162. package/src/browser-session/events.ts +164 -0
  163. package/src/browser-session/index.ts +27 -0
  164. package/src/browser-session/manager.ts +159 -0
  165. package/src/browser-session/types.ts +28 -0
  166. package/src/channels/__tests__/types.test.ts +134 -0
  167. package/src/channels/types.ts +55 -0
  168. package/src/cli/__tests__/run-assistant-command.ts +34 -7
  169. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  170. package/src/cli/commands/browser-relay.ts +339 -409
  171. package/src/cli/commands/credentials.ts +3 -3
  172. package/src/cli/commands/default-action.ts +68 -1
  173. package/src/cli/commands/email.ts +18 -13
  174. package/src/cli/commands/mcp.ts +16 -4
  175. package/src/cli/commands/oauth/__tests__/connect.test.ts +68 -41
  176. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  177. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  178. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  179. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  180. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  181. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  182. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  183. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  184. package/src/cli/commands/oauth/apps.ts +7 -4
  185. package/src/cli/commands/oauth/connect.ts +16 -2
  186. package/src/cli/commands/oauth/disconnect.ts +1 -1
  187. package/src/cli/commands/oauth/providers.ts +200 -36
  188. package/src/cli/commands/oauth/shared.ts +5 -5
  189. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  190. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  191. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  192. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  193. package/src/cli/commands/platform/index.ts +107 -10
  194. package/src/cli/commands/usage.ts +10 -9
  195. package/src/cli/lib/daemon-credential-client.ts +4 -0
  196. package/src/cli/program.ts +10 -3
  197. package/src/config/assistant-feature-flags.ts +59 -55
  198. package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
  199. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  200. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  201. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  202. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  203. package/src/config/bundled-skills/document/SKILL.md +4 -0
  204. package/src/config/bundled-skills/gmail/SKILL.md +12 -7
  205. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  206. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  207. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  208. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  209. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  210. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  211. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  212. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  213. package/src/config/env-registry.ts +14 -0
  214. package/src/config/env.ts +21 -0
  215. package/src/config/feature-flag-registry.json +46 -7
  216. package/src/config/loader.ts +56 -1
  217. package/src/config/sanitize-for-transfer.ts +47 -0
  218. package/src/config/schema.ts +46 -5
  219. package/src/config/schemas/host-browser.ts +66 -0
  220. package/src/config/schemas/memory-lifecycle.ts +1 -1
  221. package/src/config/schemas/memory-retrieval.ts +103 -0
  222. package/src/config/schemas/security.ts +0 -6
  223. package/src/config/schemas/services.ts +16 -0
  224. package/src/config/types.ts +0 -1
  225. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  226. package/src/context/window-manager.ts +19 -1
  227. package/src/credential-execution/approval-bridge.ts +49 -16
  228. package/src/credential-execution/managed-catalog.ts +3 -7
  229. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  230. package/src/daemon/app-source-watcher.ts +35 -0
  231. package/src/daemon/config-watcher.ts +6 -2
  232. package/src/daemon/context-overflow-approval.ts +5 -1
  233. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  234. package/src/daemon/conversation-agent-loop.ts +74 -19
  235. package/src/daemon/conversation-attachments.ts +40 -1
  236. package/src/daemon/conversation-messaging.ts +3 -0
  237. package/src/daemon/conversation-process.ts +66 -3
  238. package/src/daemon/conversation-queue-manager.ts +8 -0
  239. package/src/daemon/conversation-runtime-assembly.ts +159 -20
  240. package/src/daemon/conversation-surfaces.ts +78 -12
  241. package/src/daemon/conversation-tool-setup.ts +74 -11
  242. package/src/daemon/conversation-workspace.ts +12 -0
  243. package/src/daemon/conversation.ts +227 -11
  244. package/src/daemon/date-context.ts +10 -10
  245. package/src/daemon/first-greeting.ts +3 -2
  246. package/src/daemon/handlers/conversations.ts +9 -139
  247. package/src/daemon/handlers/shared.ts +65 -0
  248. package/src/daemon/handlers/skills.ts +232 -37
  249. package/src/daemon/host-bash-proxy.ts +48 -13
  250. package/src/daemon/host-browser-proxy.ts +191 -0
  251. package/src/daemon/host-cu-proxy.ts +36 -11
  252. package/src/daemon/host-file-proxy.ts +57 -9
  253. package/src/daemon/lifecycle.ts +86 -12
  254. package/src/daemon/message-protocol.ts +7 -0
  255. package/src/daemon/message-types/conversations.ts +59 -13
  256. package/src/daemon/message-types/host-browser.ts +100 -0
  257. package/src/daemon/message-types/messages.ts +5 -6
  258. package/src/daemon/message-types/notifications.ts +12 -0
  259. package/src/daemon/message-types/settings.ts +12 -0
  260. package/src/daemon/message-types/skills.ts +10 -0
  261. package/src/daemon/message-types/subagents.ts +2 -0
  262. package/src/daemon/server.ts +112 -35
  263. package/src/daemon/tool-side-effects.ts +6 -0
  264. package/src/daemon/transport-hints.ts +14 -0
  265. package/src/inbound/platform-callback-registration.ts +18 -17
  266. package/src/index.ts +1 -1
  267. package/src/mcp/client.ts +59 -24
  268. package/src/memory/app-store.ts +31 -1
  269. package/src/memory/conversation-crud.ts +38 -10
  270. package/src/memory/conversation-directories.ts +39 -0
  271. package/src/memory/conversation-group-migration.ts +65 -5
  272. package/src/memory/conversation-starters-cadence.ts +76 -0
  273. package/src/memory/conversation-title-service.ts +5 -2
  274. package/src/memory/db-init.ts +12 -0
  275. package/src/memory/embedding-backend.test.ts +75 -0
  276. package/src/memory/embedding-backend.ts +131 -5
  277. package/src/memory/embedding-gemini.test.ts +54 -0
  278. package/src/memory/embedding-gemini.ts +20 -9
  279. package/src/memory/embedding-local.ts +177 -18
  280. package/src/memory/graph/capability-seed.ts +3 -5
  281. package/src/memory/graph/consolidation.ts +10 -23
  282. package/src/memory/graph/extraction-job.ts +15 -0
  283. package/src/memory/graph/retriever.ts +40 -22
  284. package/src/memory/graph/store.test.ts +7 -3
  285. package/src/memory/graph/store.ts +47 -12
  286. package/src/memory/group-crud.ts +25 -9
  287. package/src/memory/llm-usage-store.ts +45 -4
  288. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  289. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  290. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  291. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  292. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  293. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  294. package/src/memory/migrations/index.ts +6 -0
  295. package/src/memory/migrations/registry.ts +8 -0
  296. package/src/memory/schema/conversations.ts +1 -0
  297. package/src/memory/schema/oauth.ts +18 -13
  298. package/src/messaging/provider.ts +1 -1
  299. package/src/notifications/broadcaster.ts +6 -0
  300. package/src/notifications/conversation-pairing.ts +12 -4
  301. package/src/notifications/emit-signal.ts +14 -0
  302. package/src/notifications/signal.ts +11 -0
  303. package/src/oauth/AGENTS.md +76 -0
  304. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  305. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  306. package/src/oauth/byo-connection.test.ts +8 -8
  307. package/src/oauth/byo-connection.ts +7 -7
  308. package/src/oauth/connect-orchestrator.ts +23 -21
  309. package/src/oauth/connect-types.ts +3 -3
  310. package/src/oauth/connection-resolver.test.ts +17 -4
  311. package/src/oauth/connection-resolver.ts +16 -16
  312. package/src/oauth/connection.ts +1 -1
  313. package/src/oauth/manual-token-connection.ts +13 -13
  314. package/src/oauth/oauth-store.ts +214 -100
  315. package/src/oauth/platform-connection.test.ts +5 -5
  316. package/src/oauth/platform-connection.ts +4 -4
  317. package/src/oauth/provider-serializer.ts +31 -5
  318. package/src/oauth/revoke.ts +76 -0
  319. package/src/oauth/seed-providers.ts +127 -87
  320. package/src/oauth/token-persistence.ts +1 -1
  321. package/src/permissions/checker.ts +3 -3
  322. package/src/permissions/defaults.ts +7 -8
  323. package/src/permissions/permission-mode.ts +4 -11
  324. package/src/permissions/prompter.ts +13 -3
  325. package/src/permissions/v2-consent-policy.ts +87 -0
  326. package/src/platform/client.ts +1 -1
  327. package/src/prompts/system-prompt.ts +18 -21
  328. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  329. package/src/prompts/templates/BOOTSTRAP.md +59 -96
  330. package/src/prompts/templates/SOUL.md +11 -11
  331. package/src/providers/anthropic/client.ts +1 -0
  332. package/src/providers/types.ts +1 -1
  333. package/src/runtime/AGENTS.md +23 -0
  334. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  335. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  336. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  337. package/src/runtime/assistant-event-hub.ts +24 -2
  338. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  339. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  341. package/src/runtime/auth/middleware.ts +98 -0
  342. package/src/runtime/auth/route-policy.ts +6 -7
  343. package/src/runtime/auth/token-service.ts +8 -0
  344. package/src/runtime/capability-tokens.ts +414 -0
  345. package/src/runtime/channel-approvals.ts +18 -5
  346. package/src/runtime/chrome-extension-registry.ts +332 -0
  347. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  348. package/src/runtime/guardian-decision-types.ts +7 -0
  349. package/src/runtime/http-server.ts +425 -70
  350. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  351. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  352. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  353. package/src/runtime/migrations/migration-transport.ts +6 -0
  354. package/src/runtime/migrations/migration-wizard.ts +22 -2
  355. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  356. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  357. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  358. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  359. package/src/runtime/pending-interactions.ts +29 -13
  360. package/src/runtime/routes/approval-routes.ts +90 -16
  361. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  362. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  363. package/src/runtime/routes/conversation-analysis-routes.ts +18 -5
  364. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  365. package/src/runtime/routes/conversation-routes.ts +308 -28
  366. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  367. package/src/runtime/routes/group-routes.ts +22 -8
  368. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  369. package/src/runtime/routes/host-browser-routes.ts +279 -0
  370. package/src/runtime/routes/host-file-routes.ts +9 -1
  371. package/src/runtime/routes/identity-routes.ts +259 -16
  372. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  373. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  374. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  375. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  376. package/src/runtime/routes/log-export-routes.ts +60 -25
  377. package/src/runtime/routes/memory-item-routes.ts +1 -7
  378. package/src/runtime/routes/migration-routes.ts +87 -2
  379. package/src/runtime/routes/oauth-apps.ts +15 -17
  380. package/src/runtime/routes/oauth-providers.ts +4 -0
  381. package/src/runtime/routes/schedule-routes.ts +24 -11
  382. package/src/runtime/routes/settings-routes.ts +9 -97
  383. package/src/runtime/routes/skills-routes.ts +52 -2
  384. package/src/runtime/routes/subagents-routes.ts +14 -10
  385. package/src/runtime/routes/usage-routes.ts +8 -7
  386. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  387. package/src/runtime/routes/workspace-routes.ts +8 -1
  388. package/src/runtime/routes/workspace-utils.ts +2 -0
  389. package/src/schedule/scheduler.ts +7 -5
  390. package/src/security/ces-credential-client.ts +20 -0
  391. package/src/security/ces-rpc-credential-backend.ts +17 -0
  392. package/src/security/credential-backend.ts +5 -0
  393. package/src/security/oauth2.ts +42 -25
  394. package/src/security/secure-keys.ts +118 -25
  395. package/src/security/token-manager.ts +23 -10
  396. package/src/skills/catalog-files.ts +492 -0
  397. package/src/skills/inline-command-runner.ts +12 -14
  398. package/src/subagent/manager.ts +131 -26
  399. package/src/subagent/types.ts +19 -0
  400. package/src/tools/apps/executors.ts +11 -2
  401. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  402. package/src/tools/browser/auth-detector.ts +43 -12
  403. package/src/tools/browser/browser-execution.ts +645 -340
  404. package/src/tools/browser/browser-manager.ts +36 -12
  405. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  406. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  407. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  408. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  409. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  410. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  411. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  412. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  413. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  414. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  415. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  416. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  417. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  418. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  419. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  420. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  421. package/src/tools/browser/cdp-client/errors.ts +34 -0
  422. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  423. package/src/tools/browser/cdp-client/factory.ts +204 -0
  424. package/src/tools/browser/cdp-client/index.ts +14 -0
  425. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  426. package/src/tools/browser/cdp-client/types.ts +52 -0
  427. package/src/tools/filesystem/edit.ts +1 -1
  428. package/src/tools/filesystem/list.ts +1 -1
  429. package/src/tools/filesystem/read.ts +1 -1
  430. package/src/tools/filesystem/write.ts +2 -1
  431. package/src/tools/host-filesystem/edit.ts +1 -1
  432. package/src/tools/host-filesystem/read.ts +12 -15
  433. package/src/tools/host-filesystem/write.ts +1 -1
  434. package/src/tools/host-terminal/host-shell.ts +21 -16
  435. package/src/tools/permission-checker.ts +77 -100
  436. package/src/tools/registry.ts +0 -2
  437. package/src/tools/secret-detection-handler.ts +34 -1
  438. package/src/tools/shared/filesystem/image-read.ts +61 -40
  439. package/src/tools/skills/sandbox-runner.ts +3 -6
  440. package/src/tools/subagent/spawn.ts +47 -3
  441. package/src/tools/subagent/status.ts +2 -0
  442. package/src/tools/system/register.ts +2 -16
  443. package/src/tools/terminal/safe-env.ts +7 -0
  444. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  445. package/src/tools/terminal/sandbox.ts +4 -1
  446. package/src/tools/terminal/shell.ts +24 -21
  447. package/src/tools/tool-approval-handler.ts +48 -2
  448. package/src/tools/types.ts +2 -3
  449. package/src/util/platform.ts +14 -19
  450. package/src/watcher/provider-types.ts +1 -1
  451. package/src/workspace/migrations/029-seed-pkb.ts +1 -0
  452. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  453. package/src/workspace/migrations/registry.ts +2 -0
  454. package/src/workspace/top-level-renderer.ts +19 -1
  455. package/src/__tests__/chrome-cdp.test.ts +0 -419
  456. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  457. package/src/__tests__/permission-mode-store.test.ts +0 -277
  458. package/src/browser-extension-relay/protocol.ts +0 -63
  459. package/src/browser-extension-relay/server.ts +0 -203
  460. package/src/config/schemas/sandbox.ts +0 -14
  461. package/src/permissions/permission-mode-store.ts +0 -180
  462. package/src/tools/browser/chrome-cdp.ts +0 -239
  463. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -20,7 +20,6 @@ import { existsSync, readFileSync } from "node:fs";
20
20
  import { homedir } from "node:os";
21
21
  import { dirname, join } from "node:path";
22
22
 
23
- import { getIsContainerized } from "./env-registry.js";
24
23
  import type { AssistantConfig } from "./schema.js";
25
24
 
26
25
  // ---------------------------------------------------------------------------
@@ -173,61 +172,49 @@ function loadOverridesFromFile(): Record<string, boolean> {
173
172
  }
174
173
 
175
174
  /**
176
- * Load override values from the gateway via synchronous HTTP call.
175
+ * Fetch override values from the gateway via async HTTP.
177
176
  *
178
- * Follows the trust-client pattern: uses `Bun.spawnSync` + `curl` to make
179
- * a blocking GET request to the gateway's feature-flags endpoint. The
180
- * gateway returns `{ flags: Array<{ key, enabled, ... }> }` and we extract
181
- * just the key → enabled map.
177
+ * Returns the gateway's merged feature flag map (persisted > remote >
178
+ * registry), or an empty record on any failure (network, auth, parse).
182
179
  */
183
- function loadOverridesFromGateway(): Record<string, boolean> {
180
+ async function fetchOverridesFromGateway(): Promise<Record<string, boolean>> {
184
181
  try {
185
182
  // Lazy-import to avoid circular dependency and keep this module
186
183
  // importable from bootstrap code when not in containerized mode.
187
184
  const { getGatewayInternalBaseUrl } =
188
185
  // eslint-disable-next-line @typescript-eslint/no-require-imports
189
186
  require("./env.js") as typeof import("./env.js");
190
- const { mintEdgeRelayToken } =
187
+ const {
188
+ mintEdgeRelayToken,
189
+ isSigningKeyInitialized,
190
+ initAuthSigningKey,
191
+ resolveSigningKey,
192
+ } =
191
193
  // eslint-disable-next-line @typescript-eslint/no-require-imports
192
194
  require("../runtime/auth/token-service.js") as typeof import("../runtime/auth/token-service.js");
193
195
 
196
+ // CLI subprocesses don't run daemon startup, so the signing key
197
+ // may not be initialized yet. Initialize it now so mintEdgeRelayToken
198
+ // can produce a valid JWT for the gateway request.
199
+ if (!isSigningKeyInitialized()) {
200
+ initAuthSigningKey(resolveSigningKey());
201
+ }
202
+
194
203
  const url = `${getGatewayInternalBaseUrl()}/v1/feature-flags`;
195
204
  const token = mintEdgeRelayToken();
196
205
 
197
- const proc = Bun.spawnSync(
198
- [
199
- "curl",
200
- "-s",
201
- "-S",
202
- "-X",
203
- "GET",
204
- "--max-time",
205
- "10",
206
- "-H",
207
- `Authorization: Bearer ${token}`,
208
- "-H",
209
- "Accept: application/json",
210
- "-w",
211
- "\n%{http_code}",
212
- url,
213
- ],
214
- { stdout: "pipe", stderr: "pipe" },
215
- );
216
-
217
- if (proc.exitCode !== 0) return {};
218
-
219
- const output = proc.stdout.toString().trim();
220
- const lastNewline = output.lastIndexOf("\n");
221
- const responseBody = lastNewline >= 0 ? output.slice(0, lastNewline) : "";
222
- const statusCode = parseInt(
223
- lastNewline >= 0 ? output.slice(lastNewline + 1) : output,
224
- 10,
225
- );
226
-
227
- if (statusCode < 200 || statusCode >= 300) return {};
228
- if (!responseBody) return {};
229
-
230
- const parsed = JSON.parse(responseBody) as {
206
+ const response = await fetch(url, {
207
+ method: "GET",
208
+ headers: {
209
+ Authorization: `Bearer ${token}`,
210
+ Accept: "application/json",
211
+ },
212
+ signal: AbortSignal.timeout(10_000),
213
+ });
214
+
215
+ if (!response.ok) return {};
216
+
217
+ const parsed = (await response.json()) as {
231
218
  flags?: Array<{ key: string; enabled: boolean }>;
232
219
  };
233
220
  if (!Array.isArray(parsed.flags)) return {};
@@ -245,25 +232,42 @@ function loadOverridesFromGateway(): Record<string, boolean> {
245
232
  }
246
233
 
247
234
  /**
248
- * Load overrides, preferring the gateway HTTP API.
235
+ * Pre-populate the override cache from the gateway (async).
249
236
  *
250
- * In containerized mode, always uses the gateway. In local mode, tries
251
- * the gateway first and falls back to `loadOverridesFromFile()` when
252
- * the gateway is not yet available (startup race).
237
+ * Call this once during startup (daemon or CLI entry) before any sync
238
+ * `isAssistantFeatureFlagEnabled` calls. In containerized mode, always
239
+ * uses the gateway. In local mode, falls back to the local file when
240
+ * the gateway is unreachable.
253
241
  *
254
- * Results are cached at module level.
242
+ * On failure, the cache is left unset so subsequent sync calls fall
243
+ * through to the file-based fallback rather than caching an empty map
244
+ * that masks all overrides for the process lifetime.
255
245
  */
256
- function loadOverrides(): Record<string, boolean> {
257
- if (cachedOverrides != null) return cachedOverrides;
258
-
259
- const gatewayOverrides = loadOverridesFromGateway();
260
- if (Object.keys(gatewayOverrides).length > 0 || getIsContainerized()) {
246
+ export async function initFeatureFlagOverrides(): Promise<void> {
247
+ const gatewayOverrides = await fetchOverridesFromGateway();
248
+ if (Object.keys(gatewayOverrides).length > 0) {
261
249
  cachedOverrides = gatewayOverrides;
262
- return cachedOverrides;
250
+ return;
263
251
  }
264
252
 
265
- // Graceful fallback: in local mode, if the gateway hasn't started yet
266
- // (empty response), read overrides from file as a temporary measure.
253
+ // Gateway returned empty or failed. Leave the cache unset so
254
+ // loadOverrides() falls through to file on the next sync read,
255
+ // regardless of containerized vs local mode.
256
+ }
257
+
258
+ /**
259
+ * Read cached overrides synchronously.
260
+ *
261
+ * If `initFeatureFlagOverrides()` was called at startup, this returns the
262
+ * pre-populated cache. Otherwise falls back to the local file — this
263
+ * ensures the resolver never blocks on a network call.
264
+ */
265
+ function loadOverrides(): Record<string, boolean> {
266
+ if (cachedOverrides != null) return cachedOverrides;
267
+
268
+ // Cache not yet populated (initFeatureFlagOverrides wasn't called or
269
+ // hasn't finished). Fall back to the local file so the resolver still
270
+ // works, just without gateway data.
267
271
  cachedOverrides = loadOverridesFromFile();
268
272
  return cachedOverrides;
269
273
  }
@@ -8,6 +8,10 @@ metadata:
8
8
  display-name: "App Builder"
9
9
  includes:
10
10
  - "frontend-design"
11
+ activation-hints:
12
+ - "User asks to build an app, landing page, website, dashboard, tool, calculator, game, tracker, or interactive page"
13
+ - "User asks to visualize data or says 'let's visualize this' — use the app sandbox to build interactive visualizations"
14
+ - "ALWAYS prefer the app sandbox over building standalone web apps, local servers, or outputting raw HTML/CSS/JS in chat — even when the user says 'make this an app' or 'turn this into an app'"
11
15
  ---
12
16
 
13
17
  You are an expert app builder and visual designer. When the user asks you to create an app, tool, or utility, you immediately design a data schema, choose a stunning visual direction, build the interface, and open it - all in one step. You don't discuss or ask for permission to be creative. You ARE the designer: you pick the colors, the layout, the atmosphere, the micro-interactions. Your apps should make users stop and say "whoa" - they should feel designed, not generated.
@@ -173,13 +177,16 @@ export const Header: FunctionComponent<Props> = ({ title, count }) => {
173
177
 
174
178
  **CSS:** Import CSS files directly in TSX (`import './styles.css'`). You can also use inline styles via the `style` attribute on JSX elements.
175
179
 
176
- **Data bridge:** The same `window.vellum.data` API works in TSX components - call it from `useEffect` hooks or event handlers:
180
+ **Custom routes in TSX:** Use `window.vellum.fetch()` to call custom route handlers from components see the [Custom route handlers](#custom-route-handlers-user-defined-routes) section for full details:
177
181
 
178
182
  ```tsx
179
- const [records, setRecords] = useState<Record[]>([]);
183
+ const [items, setItems] = useState<Item[]>([]);
180
184
 
181
185
  useEffect(() => {
182
- window.vellum.data.query().then(setRecords).catch(console.error);
186
+ window.vellum.fetch("/v1/x/items")
187
+ .then((res) => (res.ok ? res.json() : Promise.reject(res.status)))
188
+ .then(setItems)
189
+ .catch(console.error);
183
190
  }, []);
184
191
  ```
185
192
 
@@ -211,7 +218,10 @@ export const App: FunctionComponent = () => {
211
218
  const [records, setRecords] = useState([]);
212
219
 
213
220
  useEffect(() => {
214
- window.vellum.data.query().then(setRecords);
221
+ window.vellum.fetch("/v1/x/projects")
222
+ .then((res) => res.ok ? res.json() : Promise.reject(res.status))
223
+ .then(setRecords)
224
+ .catch(console.error);
215
225
  }, []);
216
226
 
217
227
  return (
@@ -309,131 +319,15 @@ window.addEventListener("vellum-theme-change", (e) => {
309
319
 
310
320
  #### Widget component library
311
321
 
312
- A CSS/JS widget library is auto-injected alongside the design system. Use these for standard UI patterns - skip them when custom HTML serves the user better.
313
-
314
- **Layout widgets** (class names, infer HTML structure):
315
-
316
- | Widget | Purpose |
317
- | ------------------------------------------------------------ | -------------------------------------------------------------- |
318
- | `.v-metric-card` (`.v-metric-grid`) | Big number with emoji icon, label, trend |
319
- | `.v-data-table` | Sortable table with sticky header, `th[data-sortable]` |
320
- | `.v-tabs` / `.v-tab-bar` / `.v-tab-panel` | Tab navigation with keyboard support |
321
- | `.v-accordion` / `.v-accordion-item` | Collapsible sections |
322
- | `.v-search-bar` | Search input with clear button |
323
- | `.v-empty-state` | No-data placeholder with CTA |
324
- | `.v-timeline` / `.v-timeline-entry` | Vertical timeline (`.active`/`.success`/`.error`) |
325
- | `.v-action-list` / `.v-action-list-item` | Rows with per-item actions |
326
- | `.v-card-grid` | Responsive card grid |
327
- | `.v-progress-bar` / `.v-progress-track` / `.v-progress-fill` | Horizontal progress |
328
- | `.v-status-badge` | Colored pill with dot (`.success`/`.error`/`.warning`/`.info`) |
329
- | `.v-stat-row` / `.v-stat` | Horizontal label-value pairs |
330
- | `.v-toast` | Notification banner - prefer `vellum.widgets.toast()` |
331
- | `.v-avatar-row` | Contact/team display |
332
- | `.v-tag-group` | Wrapping tag row |
333
-
334
- **Domain-specific widgets** (class names, infer HTML structure):
335
-
336
- | Widget | Purpose |
337
- | ------------------ | ---------------------- |
338
- | `.v-weather-card` | Temperature + forecast |
339
- | `.v-stock-ticker` | Price display + chart |
340
- | `.v-flight-card` | Flight info with route |
341
- | `.v-billing-chart` | Usage/billing display |
342
- | `.v-boarding-pass` | Pass-styled layout |
343
- | `.v-itinerary` | Day-by-day travel plan |
344
- | `.v-receipt` | Receipt layout |
345
- | `.v-invoice` | Formal invoice |
346
-
347
- **Content & landing page components** (class names, infer HTML structure):
348
-
349
- | Widget | Purpose |
350
- | ------------------------------------------------ | --------------------------------------------------- |
351
- | `.v-hero` / `.v-hero-badge` / `.v-hero-subtitle` | Hero banner with gradient, trust badge, accent word |
352
- | `.v-section-header` / `.v-section-label` | Section intro with label |
353
- | `.v-feature-grid` / `.v-feature-card` | Feature showcase with hover lift |
354
- | `.v-pullquote` | Blockquote with gradient accent border |
355
- | `.v-comparison` | Before/after cards (`.before`/`.after`) |
356
- | `.v-page` | Centered container (max-width 600px) |
357
- | `.v-gradient-text` | Accent-colored gradient text |
358
- | `.v-animate-in` | Staggered fade-in for children |
359
-
360
- #### Widget JavaScript utilities
361
-
362
- Interactive utilities at `window.vellum.widgets.*`:
363
-
364
- **Charts** (always use these instead of hand-coding SVG/CSS charts):
322
+ A CSS/JS widget library is auto-injected alongside the design system. Use `.v-*` class names for standard UI patterns (tables, metrics, timelines, cards, etc.) and `window.vellum.widgets.*` JS utilities for charts, data formatting, and interactive behaviors. **ALWAYS use `vellum.widgets.*` chart functions** instead of hand-coding SVG/CSS charts.
365
323
 
366
- ```javascript
367
- vellum.widgets.sparkline("container-id", [10, 25, 15, 30], {
368
- width: 200,
369
- height: 40,
370
- color: "var(--v-success)",
371
- strokeWidth: 2,
372
- fill: true,
373
- });
374
- vellum.widgets.barChart(
375
- "container-id",
376
- [
377
- { label: "Jan", value: 120 },
378
- { label: "Feb", value: 180, color: "var(--v-success)" },
379
- ],
380
- {
381
- width: 400,
382
- height: 200,
383
- showLabels: true,
384
- showValues: true,
385
- horizontal: false,
386
- },
387
- );
388
- vellum.widgets.lineChart(
389
- "container-id",
390
- [
391
- { label: "Mon", value: 42 },
392
- { label: "Tue", value: 58 },
393
- ],
394
- { width: 400, height: 200, showDots: true, showGrid: true, gridLines: 4 },
395
- );
396
- vellum.widgets.progressRing("container-id", 75, {
397
- size: 100,
398
- strokeWidth: 8,
399
- color: "var(--v-success)",
400
- label: "75%",
401
- });
402
- ```
403
-
404
- **Data Formatting:**
405
-
406
- ```javascript
407
- vellum.widgets.formatCurrency(1234.56, "USD"); // "$1,234.56"
408
- vellum.widgets.formatDate("2025-01-15", "relative"); // "3d ago"
409
- vellum.widgets.formatDate("2025-01-15", "short"); // "1/15/25"
410
- vellum.widgets.formatNumber(1234567, { compact: true }); // "1.2M"
411
- ```
324
+ For the full widget reference (class names, JS APIs, chart functions, formatting utilities), see **[Widget Component Library](references/WIDGETS.md)**.
412
325
 
413
- **Interactive Behaviors:**
326
+ #### Data bridge API (deprecated)
414
327
 
415
- ```javascript
416
- vellum.widgets.sortTable("table-id"); // Wire th[data-sortable] click-to-sort
417
- vellum.widgets.filterTable("table-id", "input-id"); // Live text search
418
- vellum.widgets.tabs("tabs-id"); // Tab switching + keyboard nav
419
- vellum.widgets.accordion("accordion-id", { allowMultiple: true });
420
- vellum.widgets.multiSelect("table-id"); // Checkboxes + select-all
421
- vellum.widgets.toast("Saved!", "success", 4000); // Auto-dismiss notification
422
- vellum.widgets.countdown("timer-el", "2025-12-31T00:00:00Z", {
423
- onComplete: () => {},
424
- });
425
- ```
426
-
427
- #### When to use widgets vs custom HTML
428
-
429
- - **Use widgets** for standard patterns - tables, metrics, timelines, notifications
430
- - **Use custom HTML** for novel or creative UIs - games, art tools, unique dashboards
431
- - **Mix freely** - widgets compose well together and with custom elements
432
- - **ALWAYS use `vellum.widgets.*` chart functions** instead of hand-coding SVG/CSS charts. They handle overflow clipping, bounds, scaling, and dark mode. Hand-coded charts break layouts.
328
+ > **Prefer custom route handlers** for new apps. The data bridge (`window.vellum.data`) only works for assistants that run on the same machine as the desktop app, which will also be deprecated soon.
433
329
 
434
- #### Data bridge API
435
-
436
- The HTML interface can read and write records via `window.vellum.data`. All methods return Promises.
330
+ The native WebView can read and write app records via `window.vellum.data`. All methods return Promises.
437
331
 
438
332
  - `window.vellum.data.query()` - Returns all records: `{ id, appId, data, createdAt, updatedAt }[]`
439
333
  - `window.vellum.data.create(data)` - Creates a record. Returns the created record.
@@ -448,9 +342,15 @@ Important:
448
342
  - All operations are async - use `async/await`
449
343
  - Wrap all calls in `try/catch`
450
344
 
345
+ #### Custom route handlers (user-defined routes)
346
+
347
+ When the app needs server-side persistence, custom API logic, or workspace file access, use **user-defined routes**. Route handlers are TypeScript/JavaScript files in the workspace `routes/` directory, served under `/v1/x/`. Call them from the frontend via `window.vellum.fetch("/v1/x/...")`. **Never use raw `fetch()` for `/v1/x/` routes** — it will fail in the sandboxed origin.
348
+
349
+ For handler conventions, examples, key rules, and frontend usage patterns, see **[Custom Route Handlers](references/CUSTOM_ROUTES.md)**.
350
+
451
351
  #### Client-side state management
452
352
 
453
- `localStorage` and `sessionStorage` are available for ephemeral UI state (filters, view modes, collapsed state, preferences, form drafts). Use `window.vellum.data` for persistent app records, `localStorage` for UI preferences.
353
+ `localStorage` and `sessionStorage` are available for ephemeral UI state (filters, view modes, collapsed state, preferences, form drafts). Use custom routes for persistent app records, `localStorage` for UI preferences.
454
354
 
455
355
  <!-- feature:app-builder-multifile:alt -->
456
356
 
@@ -467,7 +367,9 @@ let allRecords = [];
467
367
 
468
368
  async function loadRecords() {
469
369
  try {
470
- allRecords = await window.vellum.data.query();
370
+ const res = await window.vellum.fetch("/v1/x/records");
371
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
372
+ allRecords = await res.json();
471
373
  render();
472
374
  } catch (err) {
473
375
  console.error("Failed to load:", err);
@@ -556,7 +458,7 @@ Every app must meet these baselines:
556
458
 
557
459
  ## Presentation Slide Design
558
460
 
559
- Slides are a different domain from apps. Skip app-specific patterns (contextual headers, search/filter, toast notifications, form validation, data bridge). Slides are static content — build navigation and layouts with custom HTML/CSS.
461
+ Slides are a different domain from apps. Skip app-specific patterns (contextual headers, search/filter, toast notifications, form validation, custom routes). Slides are static content — build navigation and layouts with custom HTML/CSS.
560
462
 
561
463
  **Key principles:**
562
464
 
@@ -569,58 +471,16 @@ Slides are a different domain from apps. Skip app-specific patterns (contextual
569
471
 
570
472
  ## Error Handling
571
473
 
572
- - All `window.vellum.data` calls must be wrapped in `try/catch` with user-friendly feedback.
474
+ - All `window.vellum.fetch()` calls to custom routes must be wrapped in `try/catch` with user-friendly feedback. Always check `res.ok` before parsing the response body.
573
475
  - Never let a failed operation silently pass - always show a toast or inline error.
574
476
  - If the page loads with no data, show a designed empty state (`.v-empty-state`).
575
477
  - For forms, show validation errors inline next to the relevant field.
576
478
 
577
479
  ## App Interaction Hooks
578
480
 
579
- When building apps, proactively wire `sendAction` hooks so the assistant stays aware of meaningful user interactions. Two patterns are available:
580
-
581
- ### Reactive hooks
582
-
583
- Reactive hooks trigger an assistant response. Use them for moments where the assistant's input adds value - selections that need explanation, completions worth celebrating, or submissions that benefit from feedback.
584
-
585
- ```javascript
586
- // User selects a city on a map — assistant can provide insights
587
- window.vellum.sendAction('city_selected', { city: 'Tokyo' });
588
-
589
- // User submits a form — assistant can confirm and suggest next steps
590
- window.vellum.sendAction('form_submitted', { formId: 'signup', email: 'user@example.com' });
591
-
592
- // User completes a level — assistant can congratulate and hint at what's next
593
- window.vellum.sendAction('level_complete', { level: 5, score: 2400 });
594
- ```
595
-
596
- ### Silent hooks
597
-
598
- Silent hooks accumulate state without interrupting the user. The state is automatically included as context when the next reactive hook fires.
599
-
600
- ```javascript
601
- // User navigates to a new tab — no response needed, but assistant should know
602
- window.vellum.sendAction('state_update', { currentView: 'forecast', city: 'Tokyo' });
603
-
604
- // Score changes during gameplay — track silently
605
- window.vellum.sendAction('state_update', { score: 1250, lives: 2 });
606
-
607
- // User applies a filter — context for future questions
608
- window.vellum.sendAction('state_update', { filter: 'last-30-days', sortBy: 'revenue' });
609
- ```
610
-
611
- ### When to use reactive vs silent
612
-
613
- Choose based on whether the assistant's response would genuinely help the user at that moment:
614
-
615
- | App type | Silent (state accumulation) | Reactive (triggers response) |
616
- |---|---|---|
617
- | **Dashboards** | Tab navigation, filter changes, date range selection | Anomaly detected, threshold breached, data export complete |
618
- | **Games** | Score updates, move tracking, timer ticks | Level complete, achievement unlocked, game over |
619
- | **Forms & wizards** | Field focus, partial input, step navigation | Form submitted, validation failed on submit |
620
- | **Trackers** | Incremental progress, status toggles, reordering | Milestone reached, streak achieved, all items complete |
621
- | **Data explorers** | Sorting, paging, column toggling | Row selected for detail, comparison initiated |
481
+ Proactively wire `window.vellum.sendAction()` hooks so the assistant stays aware of meaningful user interactions. Two patterns: **reactive** hooks (trigger assistant response) and **silent** hooks (`state_update` — accumulate context without interrupting). Wire hooks during the initial build, don't wait for the user to ask.
622
482
 
623
- Wire hooks during the initial build - don't wait for the user to ask. Apps that communicate state back to the assistant feel alive; apps that don't feel like static pages.
483
+ For examples, reactive vs silent guidance, and per-app-type recommendations, see **[App Interaction Hooks](references/INTERACTION_HOOKS.md)**.
624
484
 
625
485
  ## Actionable UI
626
486
 
@@ -0,0 +1,105 @@
1
+ # Custom Route Handlers (User-Defined Routes)
2
+
3
+ When the app needs server-side persistence, custom API logic, or workspace file access, use **user-defined routes**. Route handlers are TypeScript or JavaScript files that live in the workspace `routes/` directory and are served under the `/v1/x/` URL path.
4
+
5
+ **Common use cases:** CRUD storage, file-based persistence, search/aggregation, external API proxying, webhook receivers.
6
+
7
+ ## Handler file convention
8
+
9
+ Each handler file exports named functions for the HTTP methods it supports (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`). Handlers use the standard Web API `Request`/`Response` signature.
10
+
11
+ ```
12
+ {workspaceDir}/routes/
13
+ items.ts # Handles /v1/x/items
14
+ items/
15
+ [id].ts # Not supported — use query params instead
16
+ index.ts # Also handles /v1/x/items (index convention)
17
+ ```
18
+
19
+ ## Example handler — JSON file persistence
20
+
21
+ ```typescript
22
+ // routes/items.ts
23
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
24
+ import { join } from "node:path";
25
+
26
+ export const description = "Item CRUD — stores records as a JSON file";
27
+
28
+ const DATA_DIR = join(process.env.VELLUM_WORKSPACE_DIR!, "data");
29
+ const DATA_FILE = join(DATA_DIR, "items.json");
30
+
31
+ function loadItems(): Array<Record<string, unknown>> {
32
+ mkdirSync(DATA_DIR, { recursive: true });
33
+ if (!existsSync(DATA_FILE)) return [];
34
+ return JSON.parse(readFileSync(DATA_FILE, "utf-8"));
35
+ }
36
+
37
+ function saveItems(items: Array<Record<string, unknown>>): void {
38
+ mkdirSync(DATA_DIR, { recursive: true });
39
+ writeFileSync(DATA_FILE, JSON.stringify(items, null, 2));
40
+ }
41
+
42
+ export function GET(): Response {
43
+ return Response.json(loadItems());
44
+ }
45
+
46
+ export async function POST(request: Request): Promise<Response> {
47
+ const body = await request.json();
48
+ const items = loadItems();
49
+ const item = {
50
+ id: crypto.randomUUID(),
51
+ ...body,
52
+ createdAt: new Date().toISOString(),
53
+ };
54
+ items.push(item);
55
+ saveItems(items);
56
+ return Response.json(item, { status: 201 });
57
+ }
58
+ ```
59
+
60
+ ## Calling routes from the app frontend
61
+
62
+ Apps call custom routes via `window.vellum.fetch()` using the `/v1/x/` prefix. This authenticated wrapper automatically injects the gateway URL and auth headers so requests reach the assistant runtime. **Never use raw `fetch()` for `/v1/x/` routes** — it will fail because the app runs in a sandboxed origin.
63
+
64
+ ```typescript
65
+ // In a TSX component or HTML script
66
+ const res = await window.vellum.fetch("/v1/x/items");
67
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
68
+ const items = await res.json();
69
+
70
+ // Create a new item
71
+ const createRes = await window.vellum.fetch("/v1/x/items", {
72
+ method: "POST",
73
+ headers: { "Content-Type": "application/json" },
74
+ body: JSON.stringify({ name: "New item", status: "active" }),
75
+ });
76
+ if (!createRes.ok) throw new Error(`HTTP ${createRes.status}`);
77
+ ```
78
+
79
+ ## Key rules
80
+
81
+ - Always create the route handler files via `file_write` before calling `app_refresh`
82
+ - Export an optional `description` string for CLI discoverability (`assistant routes list`)
83
+ - Handlers have full Node.js API access — `fs`, `path`, `crypto`, etc.
84
+ - Handlers get a 30-second timeout per request
85
+ - Files are hot-reloaded on change (mtime-based cache)
86
+ - Use `.ts` (preferred) or `.js` extensions
87
+ - Route resolution: `routes/foo.ts` → `/v1/x/foo`, `routes/bar/index.ts` → `/v1/x/bar`
88
+
89
+ ## Using custom routes in TSX components
90
+
91
+ ```tsx
92
+ const [items, setItems] = useState<Item[]>([]);
93
+
94
+ useEffect(() => {
95
+ window.vellum
96
+ .fetch("/v1/x/items")
97
+ .then((res) => (res.ok ? res.json() : Promise.reject(res.status)))
98
+ .then(setItems)
99
+ .catch(console.error);
100
+ }, []);
101
+ ```
102
+
103
+ ## Error handling
104
+
105
+ All `window.vellum.fetch()` calls to custom routes must be wrapped in `try/catch` with user-friendly feedback. Always check `res.ok` before parsing the response body. Never let a failed operation silently pass — always show a toast or inline error.
@@ -0,0 +1,56 @@
1
+ # App Interaction Hooks
2
+
3
+ When building apps, proactively wire `sendAction` hooks so the assistant stays aware of meaningful user interactions. Two patterns are available:
4
+
5
+ ## Reactive hooks
6
+
7
+ Reactive hooks trigger an assistant response. Use them for moments where the assistant's input adds value - selections that need explanation, completions worth celebrating, or submissions that benefit from feedback.
8
+
9
+ ```javascript
10
+ // User selects a city on a map — assistant can provide insights
11
+ window.vellum.sendAction("city_selected", { city: "Tokyo" });
12
+
13
+ // User submits a form — assistant can confirm and suggest next steps
14
+ window.vellum.sendAction("form_submitted", {
15
+ formId: "signup",
16
+ email: "user@example.com",
17
+ });
18
+
19
+ // User completes a level — assistant can congratulate and hint at what's next
20
+ window.vellum.sendAction("level_complete", { level: 5, score: 2400 });
21
+ ```
22
+
23
+ ## Silent hooks
24
+
25
+ Silent hooks accumulate state without interrupting the user. The state is automatically included as context when the next reactive hook fires.
26
+
27
+ ```javascript
28
+ // User navigates to a new tab — no response needed, but assistant should know
29
+ window.vellum.sendAction("state_update", {
30
+ currentView: "forecast",
31
+ city: "Tokyo",
32
+ });
33
+
34
+ // Score changes during gameplay — track silently
35
+ window.vellum.sendAction("state_update", { score: 1250, lives: 2 });
36
+
37
+ // User applies a filter — context for future questions
38
+ window.vellum.sendAction("state_update", {
39
+ filter: "last-30-days",
40
+ sortBy: "revenue",
41
+ });
42
+ ```
43
+
44
+ ## When to use reactive vs silent
45
+
46
+ Choose based on whether the assistant's response would genuinely help the user at that moment:
47
+
48
+ | App type | Silent (state accumulation) | Reactive (triggers response) |
49
+ | ------------------- | ---------------------------------------------------- | ---------------------------------------------------------- |
50
+ | **Dashboards** | Tab navigation, filter changes, date range selection | Anomaly detected, threshold breached, data export complete |
51
+ | **Games** | Score updates, move tracking, timer ticks | Level complete, achievement unlocked, game over |
52
+ | **Forms & wizards** | Field focus, partial input, step navigation | Form submitted, validation failed on submit |
53
+ | **Trackers** | Incremental progress, status toggles, reordering | Milestone reached, streak achieved, all items complete |
54
+ | **Data explorers** | Sorting, paging, column toggling | Row selected for detail, comparison initiated |
55
+
56
+ Wire hooks during the initial build - don't wait for the user to ask. Apps that communicate state back to the assistant feel alive; apps that don't feel like static pages.