@vellumai/assistant 0.6.2 → 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 (396) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docs/architecture/memory.md +1 -1
  4. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  5. package/openapi.yaml +184 -69
  6. package/package.json +41 -41
  7. package/scripts/generate-openapi.ts +1 -2
  8. package/src/__tests__/acp-session.test.ts +43 -0
  9. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  10. package/src/__tests__/app-executors.test.ts +1 -0
  11. package/src/__tests__/app-source-watcher.test.ts +37 -11
  12. package/src/__tests__/approval-routes-http.test.ts +178 -1
  13. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  14. package/src/__tests__/browser-manager.test.ts +40 -27
  15. package/src/__tests__/catalog-files.test.ts +862 -0
  16. package/src/__tests__/channel-approvals.test.ts +53 -0
  17. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +125 -48
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  21. package/src/__tests__/context-overflow-approval.test.ts +16 -1
  22. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  24. package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
  25. package/src/__tests__/conversation-attachments.test.ts +80 -4
  26. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  27. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  28. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  29. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  30. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  31. package/src/__tests__/conversation-queue.test.ts +45 -2
  32. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  33. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  34. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  35. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  36. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  37. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  38. package/src/__tests__/conversation-store.test.ts +195 -0
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  40. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
  41. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  42. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  43. package/src/__tests__/credential-vault.test.ts +152 -13
  44. package/src/__tests__/credentials-cli.test.ts +2 -2
  45. package/src/__tests__/date-context.test.ts +4 -4
  46. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  47. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  48. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  49. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  50. package/src/__tests__/gemini-provider.test.ts +2 -2
  51. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  52. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  53. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  54. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  55. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  56. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  57. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  58. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  59. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  60. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  61. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  62. package/src/__tests__/host-browser-routes.test.ts +198 -0
  63. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  64. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  65. package/src/__tests__/host-file-proxy.test.ts +185 -1
  66. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  67. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  68. package/src/__tests__/host-shell-tool.test.ts +1 -11
  69. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  70. package/src/__tests__/integration-status.test.ts +6 -7
  71. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  72. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  73. package/src/__tests__/mcp-health-check.test.ts +10 -3
  74. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  75. package/src/__tests__/migration-export-http.test.ts +61 -2
  76. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  77. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  78. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  80. package/src/__tests__/oauth-cli.test.ts +707 -60
  81. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  82. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  83. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  84. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  85. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  86. package/src/__tests__/oauth-store.test.ts +1386 -182
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  88. package/src/__tests__/onboarding-template-contract.test.ts +75 -57
  89. package/src/__tests__/openai-provider.test.ts +2 -2
  90. package/src/__tests__/outlook-categories.test.ts +1 -1
  91. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  92. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  93. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  94. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  95. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  96. package/src/__tests__/outlook-trash.test.ts +1 -1
  97. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  98. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  99. package/src/__tests__/permission-mode.test.ts +28 -56
  100. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  101. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  102. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  103. package/src/__tests__/require-fresh-approval.test.ts +40 -1
  104. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  105. package/src/__tests__/schedule-routes.test.ts +162 -0
  106. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  107. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  108. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  109. package/src/__tests__/set-permission-mode.test.ts +13 -250
  110. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  111. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  112. package/src/__tests__/slack-channel-config.test.ts +12 -15
  113. package/src/__tests__/subagent-detail.test.ts +44 -2
  114. package/src/__tests__/subagent-disposal.test.ts +1 -0
  115. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  116. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  117. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  118. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  119. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  120. package/src/__tests__/subagent-tools.test.ts +1 -0
  121. package/src/__tests__/subagent-types.test.ts +1 -0
  122. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  123. package/src/__tests__/system-prompt.test.ts +72 -1
  124. package/src/__tests__/task-scheduler.test.ts +32 -6
  125. package/src/__tests__/telegram-config.test.ts +10 -13
  126. package/src/__tests__/terminal-tools.test.ts +9 -0
  127. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  128. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  129. package/src/__tests__/top-level-renderer.test.ts +73 -1
  130. package/src/__tests__/transport-hints-queue.test.ts +14 -29
  131. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  132. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  133. package/src/acp/client-handler.ts +30 -4
  134. package/src/agent/loop.ts +12 -6
  135. package/src/approvals/guardian-request-resolvers.ts +21 -15
  136. package/src/browser-session/__tests__/manager.test.ts +297 -0
  137. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  138. package/src/browser-session/backends/extension.ts +26 -0
  139. package/src/browser-session/backends/local.ts +24 -0
  140. package/src/browser-session/events.ts +164 -0
  141. package/src/browser-session/index.ts +27 -0
  142. package/src/browser-session/manager.ts +159 -0
  143. package/src/browser-session/types.ts +28 -0
  144. package/src/channels/__tests__/types.test.ts +134 -0
  145. package/src/channels/types.ts +53 -3
  146. package/src/cli/commands/browser-relay.ts +339 -409
  147. package/src/cli/commands/credentials.ts +3 -3
  148. package/src/cli/commands/email.ts +18 -13
  149. package/src/cli/commands/mcp.ts +16 -4
  150. package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
  151. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  152. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  153. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  154. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  155. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  156. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  157. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  158. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  159. package/src/cli/commands/oauth/apps.ts +7 -4
  160. package/src/cli/commands/oauth/connect.ts +6 -3
  161. package/src/cli/commands/oauth/disconnect.ts +1 -1
  162. package/src/cli/commands/oauth/providers.ts +200 -36
  163. package/src/cli/commands/oauth/shared.ts +5 -5
  164. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  165. package/src/cli/commands/platform/index.ts +107 -10
  166. package/src/cli/commands/usage.ts +10 -9
  167. package/src/cli/lib/daemon-credential-client.ts +4 -0
  168. package/src/cli/program.ts +1 -1
  169. package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
  170. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  171. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  172. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  173. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  174. package/src/config/bundled-skills/document/SKILL.md +4 -0
  175. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  176. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  177. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  178. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  179. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  180. package/src/config/env-registry.ts +14 -0
  181. package/src/config/env.ts +21 -0
  182. package/src/config/feature-flag-registry.json +44 -5
  183. package/src/config/loader.ts +56 -1
  184. package/src/config/sanitize-for-transfer.ts +47 -0
  185. package/src/config/schema.ts +46 -5
  186. package/src/config/schemas/host-browser.ts +66 -0
  187. package/src/config/schemas/memory-lifecycle.ts +1 -1
  188. package/src/config/schemas/memory-retrieval.ts +103 -0
  189. package/src/config/schemas/security.ts +0 -6
  190. package/src/config/schemas/services.ts +8 -0
  191. package/src/config/types.ts +0 -1
  192. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  193. package/src/context/window-manager.ts +19 -1
  194. package/src/credential-execution/approval-bridge.ts +49 -15
  195. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  196. package/src/daemon/app-source-watcher.ts +35 -0
  197. package/src/daemon/context-overflow-approval.ts +5 -0
  198. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  199. package/src/daemon/conversation-agent-loop.ts +58 -24
  200. package/src/daemon/conversation-attachments.ts +40 -0
  201. package/src/daemon/conversation-process.ts +48 -1
  202. package/src/daemon/conversation-runtime-assembly.ts +118 -36
  203. package/src/daemon/conversation-surfaces.ts +37 -36
  204. package/src/daemon/conversation-tool-setup.ts +74 -8
  205. package/src/daemon/conversation-workspace.ts +12 -0
  206. package/src/daemon/conversation.ts +226 -8
  207. package/src/daemon/date-context.ts +10 -10
  208. package/src/daemon/first-greeting.ts +3 -2
  209. package/src/daemon/handlers/conversations.ts +9 -140
  210. package/src/daemon/handlers/shared.ts +58 -0
  211. package/src/daemon/handlers/skills.ts +232 -37
  212. package/src/daemon/host-bash-proxy.ts +48 -13
  213. package/src/daemon/host-browser-proxy.ts +191 -0
  214. package/src/daemon/host-cu-proxy.ts +36 -11
  215. package/src/daemon/host-file-proxy.ts +57 -9
  216. package/src/daemon/lifecycle.ts +65 -11
  217. package/src/daemon/message-protocol.ts +7 -0
  218. package/src/daemon/message-types/conversations.ts +55 -13
  219. package/src/daemon/message-types/host-browser.ts +100 -0
  220. package/src/daemon/message-types/messages.ts +5 -5
  221. package/src/daemon/message-types/skills.ts +10 -0
  222. package/src/daemon/message-types/subagents.ts +2 -0
  223. package/src/daemon/server.ts +92 -12
  224. package/src/daemon/tool-side-effects.ts +6 -0
  225. package/src/daemon/transport-hints.ts +5 -24
  226. package/src/inbound/platform-callback-registration.ts +18 -17
  227. package/src/mcp/client.ts +59 -24
  228. package/src/memory/app-store.ts +31 -1
  229. package/src/memory/conversation-crud.ts +23 -0
  230. package/src/memory/conversation-starters-cadence.ts +76 -0
  231. package/src/memory/conversation-title-service.ts +5 -2
  232. package/src/memory/db-init.ts +12 -0
  233. package/src/memory/embedding-backend.test.ts +75 -0
  234. package/src/memory/embedding-backend.ts +131 -5
  235. package/src/memory/embedding-gemini.test.ts +54 -0
  236. package/src/memory/embedding-gemini.ts +20 -9
  237. package/src/memory/embedding-local.ts +176 -17
  238. package/src/memory/graph/consolidation.ts +10 -23
  239. package/src/memory/graph/extraction-job.ts +15 -0
  240. package/src/memory/graph/retriever.ts +40 -22
  241. package/src/memory/graph/store.test.ts +7 -3
  242. package/src/memory/graph/store.ts +47 -12
  243. package/src/memory/llm-usage-store.ts +45 -4
  244. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  245. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  246. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  247. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  248. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  249. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  250. package/src/memory/migrations/index.ts +6 -0
  251. package/src/memory/migrations/registry.ts +8 -0
  252. package/src/memory/schema/conversations.ts +1 -0
  253. package/src/memory/schema/oauth.ts +18 -13
  254. package/src/oauth/AGENTS.md +76 -0
  255. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  256. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  257. package/src/oauth/byo-connection.test.ts +8 -8
  258. package/src/oauth/byo-connection.ts +7 -7
  259. package/src/oauth/connect-orchestrator.ts +23 -21
  260. package/src/oauth/connect-types.ts +3 -3
  261. package/src/oauth/connection-resolver.test.ts +17 -4
  262. package/src/oauth/connection-resolver.ts +16 -16
  263. package/src/oauth/connection.ts +1 -1
  264. package/src/oauth/manual-token-connection.ts +13 -13
  265. package/src/oauth/oauth-store.ts +214 -100
  266. package/src/oauth/platform-connection.test.ts +3 -3
  267. package/src/oauth/platform-connection.ts +4 -4
  268. package/src/oauth/provider-serializer.ts +31 -5
  269. package/src/oauth/revoke.ts +76 -0
  270. package/src/oauth/seed-providers.ts +126 -87
  271. package/src/oauth/token-persistence.ts +1 -1
  272. package/src/permissions/permission-mode.ts +4 -11
  273. package/src/permissions/prompter.ts +13 -1
  274. package/src/permissions/v2-consent-policy.ts +87 -0
  275. package/src/prompts/system-prompt.ts +18 -21
  276. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  277. package/src/prompts/templates/BOOTSTRAP.md +59 -105
  278. package/src/providers/anthropic/client.ts +1 -0
  279. package/src/providers/types.ts +1 -1
  280. package/src/runtime/AGENTS.md +23 -0
  281. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  282. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  283. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  284. package/src/runtime/assistant-event-hub.ts +2 -2
  285. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  286. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  287. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  288. package/src/runtime/auth/middleware.ts +98 -0
  289. package/src/runtime/auth/route-policy.ts +6 -7
  290. package/src/runtime/capability-tokens.ts +414 -0
  291. package/src/runtime/channel-approvals.ts +18 -5
  292. package/src/runtime/chrome-extension-registry.ts +332 -0
  293. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  294. package/src/runtime/guardian-decision-types.ts +7 -0
  295. package/src/runtime/http-server.ts +425 -70
  296. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  297. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  298. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  299. package/src/runtime/migrations/migration-transport.ts +6 -0
  300. package/src/runtime/migrations/migration-wizard.ts +22 -2
  301. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  302. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  303. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  304. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  305. package/src/runtime/pending-interactions.ts +29 -13
  306. package/src/runtime/routes/approval-routes.ts +90 -16
  307. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  308. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  309. package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
  310. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  311. package/src/runtime/routes/conversation-routes.ts +301 -27
  312. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  313. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  314. package/src/runtime/routes/host-browser-routes.ts +279 -0
  315. package/src/runtime/routes/host-file-routes.ts +9 -1
  316. package/src/runtime/routes/identity-routes.ts +259 -16
  317. package/src/runtime/routes/log-export-routes.ts +42 -22
  318. package/src/runtime/routes/memory-item-routes.ts +1 -7
  319. package/src/runtime/routes/migration-routes.ts +87 -2
  320. package/src/runtime/routes/oauth-apps.ts +15 -17
  321. package/src/runtime/routes/oauth-providers.ts +4 -0
  322. package/src/runtime/routes/schedule-routes.ts +24 -11
  323. package/src/runtime/routes/settings-routes.ts +9 -97
  324. package/src/runtime/routes/skills-routes.ts +52 -2
  325. package/src/runtime/routes/subagents-routes.ts +14 -10
  326. package/src/runtime/routes/usage-routes.ts +8 -7
  327. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  328. package/src/runtime/routes/workspace-routes.ts +8 -1
  329. package/src/runtime/routes/workspace-utils.ts +2 -0
  330. package/src/schedule/scheduler.ts +7 -5
  331. package/src/security/ces-credential-client.ts +20 -0
  332. package/src/security/ces-rpc-credential-backend.ts +17 -0
  333. package/src/security/credential-backend.ts +5 -0
  334. package/src/security/oauth2.ts +42 -25
  335. package/src/security/secure-keys.ts +118 -25
  336. package/src/security/token-manager.ts +23 -10
  337. package/src/skills/catalog-files.ts +492 -0
  338. package/src/subagent/manager.ts +131 -26
  339. package/src/subagent/types.ts +19 -0
  340. package/src/tools/apps/executors.ts +11 -2
  341. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  342. package/src/tools/browser/auth-detector.ts +43 -12
  343. package/src/tools/browser/browser-execution.ts +645 -340
  344. package/src/tools/browser/browser-manager.ts +36 -12
  345. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  346. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  347. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  348. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  349. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  350. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  351. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  352. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  353. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  354. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  355. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  356. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  357. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  358. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  359. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  360. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  361. package/src/tools/browser/cdp-client/errors.ts +34 -0
  362. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  363. package/src/tools/browser/cdp-client/factory.ts +204 -0
  364. package/src/tools/browser/cdp-client/index.ts +14 -0
  365. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  366. package/src/tools/browser/cdp-client/types.ts +52 -0
  367. package/src/tools/filesystem/edit.ts +1 -1
  368. package/src/tools/filesystem/list.ts +1 -1
  369. package/src/tools/filesystem/read.ts +1 -1
  370. package/src/tools/filesystem/write.ts +2 -1
  371. package/src/tools/host-filesystem/edit.ts +1 -1
  372. package/src/tools/host-filesystem/read.ts +12 -15
  373. package/src/tools/host-filesystem/write.ts +1 -1
  374. package/src/tools/host-terminal/host-shell.ts +21 -16
  375. package/src/tools/permission-checker.ts +77 -82
  376. package/src/tools/registry.ts +0 -2
  377. package/src/tools/secret-detection-handler.ts +34 -0
  378. package/src/tools/shared/filesystem/image-read.ts +61 -40
  379. package/src/tools/subagent/spawn.ts +47 -3
  380. package/src/tools/subagent/status.ts +2 -0
  381. package/src/tools/system/register.ts +2 -16
  382. package/src/tools/terminal/safe-env.ts +7 -0
  383. package/src/tools/terminal/shell.ts +21 -16
  384. package/src/tools/tool-approval-handler.ts +48 -2
  385. package/src/tools/types.ts +2 -0
  386. package/src/util/platform.ts +14 -19
  387. package/src/workspace/top-level-renderer.ts +19 -1
  388. package/src/__tests__/chrome-cdp.test.ts +0 -419
  389. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  390. package/src/__tests__/permission-mode-store.test.ts +0 -277
  391. package/src/browser-extension-relay/protocol.ts +0 -63
  392. package/src/browser-extension-relay/server.ts +0 -203
  393. package/src/config/schemas/sandbox.ts +0 -14
  394. package/src/permissions/permission-mode-store.ts +0 -180
  395. package/src/tools/browser/chrome-cdp.ts +0 -239
  396. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -0,0 +1,492 @@
1
+ /**
2
+ * catalog-files — preview file listings and single-file content for catalog
3
+ * skills, including ones that are NOT installed locally.
4
+ *
5
+ * This module is pure library code: it does NOT wire itself into any handler
6
+ * or route. Higher-level daemon handlers consume it via the exported
7
+ * `readCatalogSkillFiles` / `readCatalogSkillFileContent` functions.
8
+ *
9
+ * Data sources:
10
+ * - In `VELLUM_DEV` mode, when `<repo>/skills/<id>/` exists on disk, we
11
+ * read the skill files directly from the repo checkout (matching the
12
+ * behavior of `getCatalog()` in dev).
13
+ * - Otherwise, we call the platform preview endpoints:
14
+ * GET /v1/skills/{skill_id}/files/
15
+ * GET /v1/skills/{skill_id}/files/content/?path=...
16
+ *
17
+ * Crucially, this code does NOT extract any tar/gzip archives. Previewing a
18
+ * skill's files never installs it or touches the install flow.
19
+ */
20
+
21
+ import {
22
+ existsSync,
23
+ lstatSync,
24
+ readdirSync,
25
+ readFileSync,
26
+ realpathSync,
27
+ statSync,
28
+ } from "node:fs";
29
+ import { basename, join, posix, sep } from "node:path";
30
+
31
+ import { getPlatformBaseUrl } from "../config/env.js";
32
+ import {
33
+ isTextMimeType as isTextMime,
34
+ MAX_INLINE_TEXT_SIZE,
35
+ } from "../runtime/routes/workspace-utils.js";
36
+ import { getLogger } from "../util/logger.js";
37
+ import { readPlatformToken } from "../util/platform.js";
38
+ import { getCatalog } from "./catalog-cache.js";
39
+ import { getRepoSkillsDir } from "./catalog-install.js";
40
+
41
+ const log = getLogger("catalog-files");
42
+
43
+ /**
44
+ * Classify a file as text/binary from its name alone. Used by the preview
45
+ * listings where we do not have the file's bytes on hand (platform mode) or
46
+ * where we want to defer reading content until explicitly requested (dev
47
+ * mode listings). Bun derives the mime type from the file extension, so
48
+ * this works for non-existent paths too.
49
+ */
50
+ function classifyByName(name: string): boolean {
51
+ const mime = Bun.file(name).type;
52
+ return !isTextMime(mime, name);
53
+ }
54
+
55
+ // ─── Shared types ────────────────────────────────────────────────────────────
56
+
57
+ /**
58
+ * A single file entry in a skill directory or preview listing.
59
+ *
60
+ * This module owns the canonical shape; `daemon/handlers/skills.ts`
61
+ * re-exports it so handler consumers can import it from either location.
62
+ * Keeping the definition here avoids a circular import — catalog-files
63
+ * depends on `catalog-cache.ts`, which would otherwise be reachable via
64
+ * the handler module.
65
+ */
66
+ export interface SkillFileEntry {
67
+ path: string; // relative to skill directory root (e.g. "SKILL.md", "tools/foo.ts")
68
+ name: string; // basename
69
+ size: number;
70
+ mimeType: string;
71
+ isBinary: boolean;
72
+ content: string | null; // inline text if ≤ 2 MB and text MIME, else null
73
+ }
74
+
75
+ // ─── Platform response contracts ─────────────────────────────────────────────
76
+ //
77
+ // The platform preview API uses snake_case on the wire. We map to the
78
+ // daemon's camelCase shape inside this module so nothing downstream needs to
79
+ // know about the platform contract.
80
+
81
+ interface PlatformFileListResponse {
82
+ skill_id: string;
83
+ files: Array<{ path: string; name: string; size: number; sha: string }>;
84
+ }
85
+
86
+ interface PlatformFileContentResponse {
87
+ path: string;
88
+ name: string;
89
+ size: number;
90
+ mime_type: string;
91
+ is_binary: boolean;
92
+ content: string | null;
93
+ }
94
+
95
+ // ─── Path sanitization ───────────────────────────────────────────────────────
96
+
97
+ /**
98
+ * Normalize and validate a relative path coming from untrusted input. Returns
99
+ * the normalized posix path on success, or `null` if the input is unsafe.
100
+ *
101
+ * Rules:
102
+ * - Reject empty strings and strings containing null bytes.
103
+ * - Reject absolute paths (unix or windows drive-prefixed).
104
+ * - Normalize backslashes to forward slashes, strip leading `./`, and
105
+ * run `posix.normalize` to collapse redundant segments.
106
+ * - Reject paths that escape the root (normalized result equals `..` or
107
+ * begins with `../`).
108
+ *
109
+ * The platform also validates these server-side; client-side sanitization is
110
+ * defense in depth and short-circuits obvious bad requests before a network
111
+ * round trip.
112
+ */
113
+ export function sanitizeRelativePath(rawPath: string): string | null {
114
+ if (typeof rawPath !== "string" || rawPath.length === 0) return null;
115
+ if (rawPath.includes("\0")) return null;
116
+ if (rawPath.startsWith("/")) return null;
117
+ if (/^[a-zA-Z]:[/\\]/.test(rawPath)) return null;
118
+
119
+ // Normalize separators and strip any leading "./" before posix.normalize
120
+ // (which would otherwise preserve it in some cases).
121
+ let candidate = rawPath.replace(/\\/g, "/");
122
+ while (candidate.startsWith("./")) {
123
+ candidate = candidate.slice(2);
124
+ }
125
+ if (candidate.length === 0) return null;
126
+
127
+ const normalized = posix.normalize(candidate);
128
+ if (normalized === "..") return null;
129
+ if (normalized.startsWith("../")) return null;
130
+ // posix.normalize can still return "." for purely no-op paths.
131
+ if (normalized === ".") return null;
132
+ // Reject if normalization produced an absolute or Windows-drive path.
133
+ // Covers bypasses like `.//etc/passwd` where the leading `./` strip loop
134
+ // leaves `/etc/passwd`, which `posix.normalize` then passes through as an
135
+ // absolute path. The pre-normalization absolute-path check above only
136
+ // catches inputs that were absolute to begin with.
137
+ if (normalized.startsWith("/")) return null;
138
+ if (/^[a-zA-Z]:[/\\]/.test(normalized)) return null;
139
+
140
+ return normalized;
141
+ }
142
+
143
+ // ─── Source resolution ───────────────────────────────────────────────────────
144
+
145
+ type CatalogSource =
146
+ | { kind: "dir"; dirPath: string }
147
+ | { kind: "platform"; skillId: string };
148
+
149
+ /**
150
+ * Resolve where to read files for a given catalog skill id. Performs NO
151
+ * network calls — network requests happen only inside `readCatalogSkillFiles`
152
+ * and `readCatalogSkillFileContent` on the platform path.
153
+ *
154
+ * Dev-mode safety: when a `<repoSkillsDir>/<skillId>` entry exists on disk,
155
+ * we verify that the skill root is a real directory physically located
156
+ * inside `repoSkillsDir` — rejecting symlinks and anything whose realpath
157
+ * escapes the repo skills dir. This prevents a symlinked skill root from
158
+ * pointing at an external directory and bypassing the later realpath
159
+ * containment check in `readCatalogSkillFileContent` (the check there
160
+ * derives `realRoot` from the already-resolved skill dir, so if the skill
161
+ * root itself is a symlink, `realRoot` resolves through the symlink target
162
+ * and the containment check becomes a no-op). On any violation we silently
163
+ * fall through to platform mode, which is the safe default — the dev-mode
164
+ * shortcut is an optimization, not a required code path.
165
+ */
166
+ async function resolveCatalogSource(
167
+ skillId: string,
168
+ ): Promise<CatalogSource | null> {
169
+ const catalog = await getCatalog();
170
+ const inCatalog = catalog.some((skill) => skill.id === skillId);
171
+ if (!inCatalog) return null;
172
+
173
+ const repoSkillsDir = getRepoSkillsDir();
174
+ if (repoSkillsDir) {
175
+ const candidate = join(repoSkillsDir, skillId);
176
+ if (existsSync(candidate) && isSafeDevSkillRoot(candidate, repoSkillsDir)) {
177
+ return { kind: "dir", dirPath: candidate };
178
+ }
179
+ }
180
+ return { kind: "platform", skillId };
181
+ }
182
+
183
+ /**
184
+ * Verify that a dev-mode skill root candidate is a real directory physically
185
+ * located inside `repoSkillsDir`. Returns `false` for any of:
186
+ *
187
+ * - `candidate` is itself a symbolic link (even if the target is still
188
+ * "nearby" — following it would break the realpath containment check
189
+ * in `readCatalogSkillFileContent`).
190
+ * - `candidate` is not a directory.
191
+ * - `realpath(candidate)` escapes `realpath(repoSkillsDir)`.
192
+ * - Any fs call throws (EACCES, ENOENT race, etc.).
193
+ *
194
+ * Callers should fall through to platform mode on `false`.
195
+ */
196
+ function isSafeDevSkillRoot(candidate: string, repoSkillsDir: string): boolean {
197
+ let lstat;
198
+ try {
199
+ lstat = lstatSync(candidate);
200
+ } catch {
201
+ return false;
202
+ }
203
+ if (lstat.isSymbolicLink()) return false;
204
+ if (!lstat.isDirectory()) return false;
205
+
206
+ let realCandidate: string;
207
+ let realRepoSkillsDir: string;
208
+ try {
209
+ realCandidate = realpathSync(candidate);
210
+ realRepoSkillsDir = realpathSync(repoSkillsDir);
211
+ } catch {
212
+ return false;
213
+ }
214
+ if (
215
+ !(
216
+ realCandidate === realRepoSkillsDir ||
217
+ realCandidate.startsWith(realRepoSkillsDir + sep)
218
+ )
219
+ ) {
220
+ return false;
221
+ }
222
+ return true;
223
+ }
224
+
225
+ // ─── Platform fetch helper ───────────────────────────────────────────────────
226
+
227
+ /**
228
+ * Fetch JSON from the platform preview API. Returns `null` on any failure
229
+ * (non-2xx, network error, abort). The query string is stripped from log
230
+ * messages to avoid leaking user-supplied file paths.
231
+ */
232
+ async function fetchPlatformJson<T>(
233
+ path: string,
234
+ query?: Record<string, string>,
235
+ ): Promise<T | null> {
236
+ const base = getPlatformBaseUrl();
237
+ const url = new URL(`${base}${path}`);
238
+ if (query) {
239
+ const params = new URLSearchParams(query);
240
+ url.search = params.toString();
241
+ }
242
+
243
+ const headers: Record<string, string> = { Accept: "application/json" };
244
+ const token = readPlatformToken();
245
+ if (token) {
246
+ headers["X-Conversation-Token"] = token;
247
+ }
248
+
249
+ try {
250
+ const response = await fetch(url.toString(), {
251
+ headers,
252
+ signal: AbortSignal.timeout(15000),
253
+ });
254
+ if (!response.ok) {
255
+ log.warn(
256
+ { status: response.status, path },
257
+ "Platform preview API returned non-2xx",
258
+ );
259
+ return null;
260
+ }
261
+ return (await response.json()) as T;
262
+ } catch (err) {
263
+ log.warn({ err, path }, "Platform preview API request failed");
264
+ return null;
265
+ }
266
+ }
267
+
268
+ // ─── Dev-mode directory walker ───────────────────────────────────────────────
269
+
270
+ // Directory names that are always skipped when walking a catalog skill dir in
271
+ // dev mode. Also used by `daemon/handlers/skills.ts` — both for the
272
+ // installed-skill walker and for the single-file content endpoint's
273
+ // hidden/skipped path rejection. Exported so the daemon handler can
274
+ // import this single source of truth and stay in sync.
275
+ export const SKIP_DIRS = new Set(["node_modules", "__pycache__", ".git"]);
276
+
277
+ /**
278
+ * Returns true if the given sanitized posix path contains any segment that
279
+ * is hidden (starts with `.`) or present in `SKIP_DIRS`. Used to reject
280
+ * file-content reads for paths the listing APIs intentionally hide, so
281
+ * callers cannot fetch `.env`, `.git/config`, `node_modules/...`, etc. via
282
+ * the content endpoint even though the listing never surfaces them.
283
+ *
284
+ * The input MUST already be a normalized posix path (i.e. the return value
285
+ * of `sanitizeRelativePath`). This helper does not re-normalize — it splits
286
+ * on `/` and inspects each segment directly.
287
+ */
288
+ export function hasHiddenOrSkippedSegment(sanitized: string): boolean {
289
+ const segments = sanitized.split("/");
290
+ for (const segment of segments) {
291
+ if (segment.length === 0) continue;
292
+ if (segment.startsWith(".")) return true;
293
+ if (SKIP_DIRS.has(segment)) return true;
294
+ }
295
+ return false;
296
+ }
297
+
298
+ function walkSkillDir(dir: string, rootDir: string): SkillFileEntry[] {
299
+ const out: SkillFileEntry[] = [];
300
+ let dirents;
301
+ try {
302
+ dirents = readdirSync(dir, { withFileTypes: true });
303
+ } catch {
304
+ return out;
305
+ }
306
+ for (const dirent of dirents) {
307
+ // Skip dot-prefixed entries (hidden files like `.DS_Store` and dot-dirs
308
+ // like `.git`, `.venv`). Matches the behavior of the installed-skill
309
+ // walker in `daemon/handlers/skills.ts`.
310
+ if (dirent.name.startsWith(".")) continue;
311
+ const abs = join(dir, dirent.name);
312
+ // Silently skip symlinks, sockets, devices, etc.
313
+ if (dirent.isSymbolicLink()) continue;
314
+ if (dirent.isDirectory()) {
315
+ // Skip well-known heavyweight directories (node_modules, __pycache__,
316
+ // ...) so a dev working on a catalog skill locally doesn't see
317
+ // thousands of spurious entries in the preview listing.
318
+ if (SKIP_DIRS.has(dirent.name)) continue;
319
+ out.push(...walkSkillDir(abs, rootDir));
320
+ continue;
321
+ }
322
+ if (!dirent.isFile()) continue;
323
+ try {
324
+ const stat = statSync(abs);
325
+ // Convert absolute → relative with manual separator normalization so
326
+ // the result is always posix-style regardless of the host platform.
327
+ const relFromRoot = abs.slice(rootDir.length);
328
+ const cleaned = relFromRoot.startsWith(sep)
329
+ ? relFromRoot.slice(sep.length)
330
+ : relFromRoot;
331
+ const posixPath = cleaned.split(sep).join("/");
332
+ out.push({
333
+ path: posixPath,
334
+ name: basename(posixPath),
335
+ size: stat.size,
336
+ mimeType: "",
337
+ isBinary: classifyByName(dirent.name),
338
+ content: null,
339
+ });
340
+ } catch {
341
+ // Skip unreadable files silently.
342
+ }
343
+ }
344
+ return out;
345
+ }
346
+
347
+ // ─── Public API ──────────────────────────────────────────────────────────────
348
+
349
+ /**
350
+ * List files for a catalog skill (installed or not).
351
+ *
352
+ * Returns `null` if the skill id is not in the catalog at all. Otherwise
353
+ * returns an array of `SkillFileEntry` with `content === null` for every
354
+ * entry (single-file content is fetched on demand via
355
+ * `readCatalogSkillFileContent`).
356
+ */
357
+ export async function readCatalogSkillFiles(
358
+ skillId: string,
359
+ ): Promise<SkillFileEntry[] | null> {
360
+ const source = await resolveCatalogSource(skillId);
361
+ if (!source) return null;
362
+
363
+ if (source.kind === "dir") {
364
+ const entries = walkSkillDir(source.dirPath, source.dirPath);
365
+ entries.sort((a, b) => a.path.localeCompare(b.path));
366
+ return entries;
367
+ }
368
+
369
+ const response = await fetchPlatformJson<PlatformFileListResponse>(
370
+ `/v1/skills/${encodeURIComponent(skillId)}/files/`,
371
+ );
372
+ if (!response) return null;
373
+
374
+ const entries: SkillFileEntry[] = response.files.map((file) => ({
375
+ path: file.path,
376
+ name: file.name,
377
+ size: file.size,
378
+ mimeType: "",
379
+ isBinary: classifyByName(file.name),
380
+ content: null,
381
+ }));
382
+ entries.sort((a, b) => a.path.localeCompare(b.path));
383
+ return entries;
384
+ }
385
+
386
+ /**
387
+ * Read a single file's content from a catalog skill (installed or not).
388
+ *
389
+ * Returns `null` if the skill is missing from the catalog, the path fails
390
+ * sanitization, the underlying source rejects the request, or the file does
391
+ * not exist. In dev mode, text files up to `MAX_INLINE_TEXT_SIZE` are returned
392
+ * with their UTF-8 content inline; anything larger or flagged as binary
393
+ * returns with `content === null`. In platform mode, the server enforces
394
+ * the same contract and we pass its response through unchanged.
395
+ */
396
+ export async function readCatalogSkillFileContent(
397
+ skillId: string,
398
+ relativePath: string,
399
+ ): Promise<SkillFileEntry | null> {
400
+ const sanitized = sanitizeRelativePath(relativePath);
401
+ if (!sanitized) return null;
402
+
403
+ // Defense in depth: reject any path that references a hidden or
404
+ // SKIP_DIRS segment. The daemon handler performs the same check before
405
+ // calling us, but we repeat it here so direct callers of this module
406
+ // short-circuit without a network round trip and without touching disk.
407
+ if (hasHiddenOrSkippedSegment(sanitized)) return null;
408
+
409
+ const source = await resolveCatalogSource(skillId);
410
+ if (!source) return null;
411
+
412
+ if (source.kind === "dir") {
413
+ const abs = join(source.dirPath, sanitized);
414
+ // Defense in depth: make absolutely sure the resolved absolute path is
415
+ // still inside the skill root, even after `join` normalization. This is
416
+ // a cheap lexical short-circuit that runs before any fs stat calls.
417
+ if (!(abs === source.dirPath || abs.startsWith(source.dirPath + sep))) {
418
+ return null;
419
+ }
420
+ if (!existsSync(abs)) return null;
421
+
422
+ // Reject symlinks at the target path directly: we do NOT want to follow
423
+ // a symlinked file inside a catalog skill dir out of the skill root.
424
+ let lstat;
425
+ try {
426
+ lstat = lstatSync(abs);
427
+ } catch {
428
+ return null;
429
+ }
430
+ if (lstat.isSymbolicLink()) return null;
431
+ if (!lstat.isFile()) return null;
432
+
433
+ // Also resolve any intermediate symlinks in the parent path via
434
+ // realpath and verify the result is still contained within the skill
435
+ // root's own realpath. This catches symlinked parent directories that
436
+ // the lexical check above can't see through.
437
+ let realAbs: string;
438
+ let realRoot: string;
439
+ try {
440
+ realAbs = realpathSync(abs);
441
+ realRoot = realpathSync(source.dirPath);
442
+ } catch {
443
+ return null;
444
+ }
445
+ if (!(realAbs === realRoot || realAbs.startsWith(realRoot + sep))) {
446
+ return null;
447
+ }
448
+
449
+ let stat;
450
+ try {
451
+ stat = statSync(abs);
452
+ } catch {
453
+ return null;
454
+ }
455
+ if (!stat.isFile()) return null;
456
+
457
+ const name = basename(abs);
458
+ const mimeType = Bun.file(abs).type;
459
+ const isBinary = !isTextMime(mimeType, name);
460
+ let content: string | null = null;
461
+ if (!isBinary && stat.size <= MAX_INLINE_TEXT_SIZE) {
462
+ try {
463
+ content = readFileSync(abs, "utf-8");
464
+ } catch {
465
+ content = null;
466
+ }
467
+ }
468
+ return {
469
+ path: sanitized,
470
+ name,
471
+ size: stat.size,
472
+ mimeType,
473
+ isBinary,
474
+ content,
475
+ };
476
+ }
477
+
478
+ const response = await fetchPlatformJson<PlatformFileContentResponse>(
479
+ `/v1/skills/${encodeURIComponent(skillId)}/files/content/`,
480
+ { path: sanitized },
481
+ );
482
+ if (!response) return null;
483
+
484
+ return {
485
+ path: response.path,
486
+ name: response.name,
487
+ size: response.size,
488
+ mimeType: response.mime_type,
489
+ isBinary: response.is_binary,
490
+ content: response.content,
491
+ };
492
+ }