@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
@@ -3,12 +3,13 @@
3
3
  */
4
4
 
5
5
  import { existsSync, readFileSync, statfsSync, statSync } from "node:fs";
6
- import { cpus, totalmem } from "node:os";
6
+ import { availableParallelism, cpus, totalmem } from "node:os";
7
7
  import { dirname, join } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
 
10
10
  import { z } from "zod";
11
11
 
12
+ import { getCpuLimit, getIsPlatform } from "../../config/env-registry.js";
12
13
  import { parseIdentityFields } from "../../daemon/handlers/identity.js";
13
14
  import { getProfilerRuntimeStatus } from "../../daemon/profiler-run-store.js";
14
15
  import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
@@ -54,10 +55,60 @@ interface MemoryInfo {
54
55
  maxMb: number;
55
56
  }
56
57
 
57
- // Read the container memory limit from cgroups if available, falling back to host total.
58
- // cgroups v2: /sys/fs/cgroup/memory.max (returns "max" when unlimited)
59
- // cgroups v1: /sys/fs/cgroup/memory/memory.limit_in_bytes (large sentinel when unlimited)
58
+ /**
59
+ * Parse a Kubernetes-style memory string (e.g. "3Gi", "512Mi", "1G") into bytes.
60
+ * Returns null if the value is not a recognized format.
61
+ */
62
+ function parseK8sMemoryBytes(value: string): number | null {
63
+ const match = value
64
+ .trim()
65
+ .match(/^(\d+(?:\.\d+)?)\s*(Ki|Mi|Gi|Ti|Pi|Ei|k|M|G|T|P|E|m)?$/);
66
+ if (!match) return null;
67
+ const num = parseFloat(match[1]);
68
+ const unit = match[2] ?? "";
69
+ const multipliers: Record<string, number> = {
70
+ "": 1,
71
+ m: 1e-3,
72
+ k: 1e3,
73
+ M: 1e6,
74
+ G: 1e9,
75
+ T: 1e12,
76
+ P: 1e15,
77
+ E: 1e18,
78
+ Ki: 1024,
79
+ Mi: 1024 ** 2,
80
+ Gi: 1024 ** 3,
81
+ Ti: 1024 ** 4,
82
+ Pi: 1024 ** 5,
83
+ Ei: 1024 ** 6,
84
+ };
85
+ const mult = multipliers[unit];
86
+ if (mult === undefined) return null;
87
+ const bytes = Math.round(num * mult);
88
+ return bytes > 0 ? bytes : null;
89
+ }
90
+
91
+ /**
92
+ * Read the memory limit from the VELLUM_MEMORY_LIMIT env var (K8s resource format),
93
+ * then fall back to cgroups, then to os.totalmem().
94
+ *
95
+ * In platform mode the container runs under gVisor where cgroup files may report
96
+ * the node's memory rather than the container limit. VELLUM_MEMORY_LIMIT is set
97
+ * by the StatefulSet template to the exact K8s memory limit (e.g. "3Gi").
98
+ */
60
99
  function getContainerMemoryLimitBytes(): number | null {
100
+ // 1. Prefer the explicit env var set by the platform StatefulSet template.
101
+ try {
102
+ const envLimit = process.env.VELLUM_MEMORY_LIMIT;
103
+ if (envLimit) {
104
+ const parsed = parseK8sMemoryBytes(envLimit);
105
+ if (parsed !== null) return parsed;
106
+ }
107
+ } catch {
108
+ /* env var parsing failed – fall through to cgroups */
109
+ }
110
+
111
+ // 2. Try cgroups v2.
61
112
  try {
62
113
  const v2 = readFileSync("/sys/fs/cgroup/memory.max", "utf-8").trim();
63
114
  if (v2 !== "max") {
@@ -67,6 +118,8 @@ function getContainerMemoryLimitBytes(): number | null {
67
118
  } catch {
68
119
  /* not available */
69
120
  }
121
+
122
+ // 3. Try cgroups v1.
70
123
  try {
71
124
  const v1 = readFileSync(
72
125
  "/sys/fs/cgroup/memory/memory.limit_in_bytes",
@@ -81,10 +134,56 @@ function getContainerMemoryLimitBytes(): number | null {
81
134
  return null;
82
135
  }
83
136
 
137
+ /**
138
+ * Read the container's current memory usage from cgroup files.
139
+ *
140
+ * Tries cgroups v2 (`memory.current`) first, then cgroups v1
141
+ * (`memory/memory.usage_in_bytes`), mirroring the v2-then-v1 fallback used by
142
+ * `getContainerMemoryLimitBytes`. Returns null if neither file is available
143
+ * or readable.
144
+ *
145
+ * Unlike the limit lookup, no env-var override is needed: the gVisor issue
146
+ * that motivates VELLUM_MEMORY_LIMIT is specifically about the *limit* files
147
+ * exposing the host node's memory instead of the sandbox limit. The *usage*
148
+ * files (memory.current / memory.usage_in_bytes) reflect the sandbox's own
149
+ * accounting and are accurate under gVisor.
150
+ */
151
+ function getContainerMemoryUsageBytes(): number | null {
152
+ // 1. Try cgroups v2.
153
+ try {
154
+ const v2 = readFileSync("/sys/fs/cgroup/memory.current", "utf-8").trim();
155
+ const bytes = parseInt(v2, 10);
156
+ if (!isNaN(bytes) && bytes > 0) return bytes;
157
+ } catch {
158
+ /* not available */
159
+ }
160
+
161
+ // 2. Try cgroups v1.
162
+ try {
163
+ const v1 = readFileSync(
164
+ "/sys/fs/cgroup/memory/memory.usage_in_bytes",
165
+ "utf-8",
166
+ ).trim();
167
+ const bytes = parseInt(v1, 10);
168
+ if (!isNaN(bytes) && bytes > 0) return bytes;
169
+ } catch {
170
+ /* not available */
171
+ }
172
+ return null;
173
+ }
174
+
84
175
  function getMemoryInfo(): MemoryInfo {
85
176
  const bytesToMb = (b: number) => Math.round((b / (1024 * 1024)) * 100) / 100;
177
+ // In platform-managed mode the daemon shares its Node process with whatever
178
+ // the container is doing as a whole; `process.memoryUsage().rss` only sees
179
+ // this process's resident set, which understates the container footprint
180
+ // operators care about. Read the cgroup usage file directly so /v1/health
181
+ // matches what the StatefulSet's memory limit is enforced against.
182
+ const currentBytes =
183
+ (getIsPlatform() ? getContainerMemoryUsageBytes() : null) ??
184
+ process.memoryUsage().rss;
86
185
  return {
87
- currentMb: bytesToMb(process.memoryUsage().rss),
186
+ currentMb: bytesToMb(currentBytes),
88
187
  maxMb: bytesToMb(getContainerMemoryLimitBytes() ?? totalmem()),
89
188
  };
90
189
  }
@@ -94,36 +193,180 @@ interface CpuInfo {
94
193
  maxCores: number;
95
194
  }
96
195
 
196
+ /**
197
+ * Parse a Kubernetes-style CPU string (e.g. "2000m", "1", "500m") into
198
+ * fractional cores. Returns null if the value is not a recognized format.
199
+ */
200
+ function parseK8sCpuCores(value: string): number | null {
201
+ const trimmed = value.trim();
202
+ const milliMatch = trimmed.match(/^(\d+)m$/);
203
+ if (milliMatch) {
204
+ const millis = parseInt(milliMatch[1], 10);
205
+ return millis > 0 ? millis / 1000 : null;
206
+ }
207
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
208
+ const num = parseFloat(trimmed);
209
+ return !isNaN(num) && num > 0 ? num : null;
210
+ }
211
+ return null;
212
+ }
213
+
214
+ /**
215
+ * Read the container's CPU core limit.
216
+ *
217
+ * Resolution order:
218
+ * 1. VELLUM_CPU_LIMIT env var (K8s resource format, e.g. "2000m" or "2").
219
+ * In platform mode the container runs under gVisor where cgroup files may
220
+ * report the node's CPU count rather than the sandbox limit.
221
+ * 2. cgroups v2 cpu.max (quota / period → fractional cores).
222
+ * 3. cgroups v1 cpu.cfs_quota_us / cpu.cfs_period_us.
223
+ * 4. os.cpus().length as last resort.
224
+ */
225
+ function getContainerCpuCores(): number {
226
+ // 1. Prefer the explicit env var set by the platform StatefulSet template.
227
+ try {
228
+ const envLimit = getCpuLimit();
229
+ if (envLimit) {
230
+ const parsed = parseK8sCpuCores(envLimit);
231
+ if (parsed !== null) return parsed;
232
+ }
233
+ } catch {
234
+ /* env var parsing failed – fall through */
235
+ }
236
+
237
+ // 2. Try cgroups v2: /sys/fs/cgroup/cpu.max contains "$MAX $PERIOD".
238
+ try {
239
+ const raw = readFileSync("/sys/fs/cgroup/cpu.max", "utf-8").trim();
240
+ if (!raw.startsWith("max")) {
241
+ const parts = raw.split(/\s+/);
242
+ const quota = parseInt(parts[0], 10);
243
+ const period = parseInt(parts[1], 10);
244
+ if (!isNaN(quota) && !isNaN(period) && period > 0 && quota > 0) {
245
+ const cores = quota / period;
246
+ // Sanity check: if the value looks like the node's full CPU count
247
+ // and we're on a platform pod, it's likely gVisor leaking the host value.
248
+ if (cores < cpus().length * 0.9 || !getIsPlatform()) {
249
+ return cores;
250
+ }
251
+ }
252
+ }
253
+ } catch {
254
+ /* not available */
255
+ }
256
+
257
+ // 3. Try cgroups v1.
258
+ try {
259
+ const quota = parseInt(
260
+ readFileSync("/sys/fs/cgroup/cpu/cpu.cfs_quota_us", "utf-8").trim(),
261
+ 10,
262
+ );
263
+ const period = parseInt(
264
+ readFileSync("/sys/fs/cgroup/cpu/cpu.cfs_period_us", "utf-8").trim(),
265
+ 10,
266
+ );
267
+ if (!isNaN(quota) && !isNaN(period) && period > 0 && quota > 0) {
268
+ const cores = quota / period;
269
+ if (cores < cpus().length * 0.9 || !getIsPlatform()) {
270
+ return cores;
271
+ }
272
+ }
273
+ } catch {
274
+ /* not available */
275
+ }
276
+
277
+ return cpus().length || availableParallelism();
278
+ }
279
+
280
+ /**
281
+ * Read the container's CPU usage from cgroup accounting files.
282
+ *
283
+ * Returns total CPU microseconds consumed by the container since boot.
284
+ * We use the delta between two samples to compute percentage.
285
+ */
286
+ function getContainerCpuUsageUs(): number | null {
287
+ // cgroups v2: cpu.stat has a "usage_usec" line.
288
+ try {
289
+ const stat = readFileSync("/sys/fs/cgroup/cpu.stat", "utf-8");
290
+ for (const line of stat.split("\n")) {
291
+ if (line.startsWith("usage_usec")) {
292
+ const val = parseInt(line.split(/\s+/)[1], 10);
293
+ if (!isNaN(val) && val > 0) return val;
294
+ }
295
+ }
296
+ } catch {
297
+ /* not available */
298
+ }
299
+
300
+ // cgroups v1: cpuacct.usage is in nanoseconds.
301
+ try {
302
+ const ns = parseInt(
303
+ readFileSync("/sys/fs/cgroup/cpuacct/cpuacct.usage", "utf-8").trim(),
304
+ 10,
305
+ );
306
+ if (!isNaN(ns) && ns > 0) return ns / 1000; // convert ns → µs
307
+ } catch {
308
+ /* not available */
309
+ }
310
+
311
+ return null;
312
+ }
313
+
97
314
  // Track CPU usage over a rolling window so /v1/health reports near-real-time
98
315
  // utilization instead of a lifetime average (total CPU time / total uptime).
99
316
  const CPU_SAMPLE_INTERVAL_MS = 5_000;
100
- let _lastCpuUsage: NodeJS.CpuUsage = process.cpuUsage();
317
+ let _lastProcessCpuUsage: NodeJS.CpuUsage = process.cpuUsage();
318
+ let _lastCgroupCpuUs: number | null = getContainerCpuUsageUs();
101
319
  let _lastCpuTime: number = Date.now();
102
320
  let _cachedCpuPercent = 0;
103
321
 
104
322
  // Kick off the background sampler. unref() so it never prevents process exit.
105
323
  setInterval(() => {
106
324
  const now = Date.now();
107
- const newUsage = process.cpuUsage();
108
325
  const elapsedMs = now - _lastCpuTime;
109
- if (elapsedMs > 0) {
110
- const deltaCpuUs =
111
- newUsage.user -
112
- _lastCpuUsage.user +
113
- (newUsage.system - _lastCpuUsage.system);
114
- const deltaCpuMs = deltaCpuUs / 1000;
115
- const numCores = cpus().length;
326
+ if (elapsedMs <= 0) return;
327
+
328
+ const numCores = getContainerCpuCores();
329
+
330
+ // Always sample process-level CPU so the baseline stays fresh. This
331
+ // prevents a spike if the platform cgroup path later falls back to
332
+ // process.cpuUsage() after cgroup stats were previously available.
333
+ const newProcessUsage = process.cpuUsage();
334
+ const processDeltaUs =
335
+ newProcessUsage.user -
336
+ _lastProcessCpuUsage.user +
337
+ (newProcessUsage.system - _lastProcessCpuUsage.system);
338
+ _lastProcessCpuUsage = newProcessUsage;
339
+
340
+ if (getIsPlatform()) {
341
+ // In platform mode, prefer cgroup-level CPU usage so we see the full
342
+ // container footprint, not just this process.
343
+ const cgroupUs = getContainerCpuUsageUs();
344
+ if (cgroupUs !== null && _lastCgroupCpuUs !== null) {
345
+ const deltaCpuUs = cgroupUs - _lastCgroupCpuUs;
346
+ const deltaCpuMs = deltaCpuUs / 1000;
347
+ _cachedCpuPercent =
348
+ Math.round((deltaCpuMs / (elapsedMs * numCores)) * 10000) / 100;
349
+ } else {
350
+ // cgroup CPU stats unavailable (e.g. gVisor) – fall back to process-level.
351
+ const deltaCpuMs = processDeltaUs / 1000;
352
+ _cachedCpuPercent =
353
+ Math.round((deltaCpuMs / (elapsedMs * numCores)) * 10000) / 100;
354
+ }
355
+ _lastCgroupCpuUs = cgroupUs;
356
+ } else {
357
+ // Non-platform: use process.cpuUsage() (accurate for single-process mode).
358
+ const deltaCpuMs = processDeltaUs / 1000;
116
359
  _cachedCpuPercent =
117
360
  Math.round((deltaCpuMs / (elapsedMs * numCores)) * 10000) / 100;
118
361
  }
119
- _lastCpuUsage = newUsage;
362
+
120
363
  _lastCpuTime = now;
121
364
  }, CPU_SAMPLE_INTERVAL_MS).unref();
122
365
 
123
366
  function getCpuInfo(): CpuInfo {
124
367
  return {
125
368
  currentPercent: _cachedCpuPercent,
126
- maxCores: cpus().length,
369
+ maxCores: Math.ceil(getContainerCpuCores()),
127
370
  };
128
371
  }
129
372
 
@@ -0,0 +1,104 @@
1
+ # Log Export — Workspace Allowlist Rules
2
+
3
+ `POST /v1/export` (handled by `log-export-routes.ts`) builds a tar.gz archive
4
+ from audit DB rows, daemon logs under `<workspace>/data/logs/`, and a
5
+ sanitized `config.json` snapshot. This directory
6
+ (`assistant/src/runtime/routes/log-export/`) houses the allowlist module
7
+ that governs which subpaths of the user's workspace directory
8
+ (`~/.vellum/workspace/`) are permitted to flow into that archive.
9
+
10
+ Workspace contents are **opt-in (allowlist), not opt-out**. The workspace
11
+ contains arbitrary user files — skills, hooks, routes, conversations,
12
+ credentials scaffolding, and other material the user has authored or
13
+ installed locally. Accidentally bundling any of that into a support
14
+ archive would exfiltrate data the user never intended to share. The
15
+ default must therefore be "nothing from the workspace ships" and each
16
+ individual entry that _does_ ship must be justified against the rules
17
+ below.
18
+
19
+ ## Rule 1 — Prefer time-filterable data
20
+
21
+ Only allowlist a workspace subpath if its contents can be narrowed to the
22
+ `[startTime, endTime]` window carried on the export request.
23
+
24
+ - When the data is organized as per-record files or per-record
25
+ directories whose **names encode a timestamp**, filter by parsing the
26
+ name. The canonical example is the per-conversation directory layout
27
+ where each directory is named `<ISO-with-dashes>_<conversationId>`
28
+ (the ISO date comes first so ordinary lexicographic comparison yields
29
+ chronological order, and colons in the ISO string are replaced with
30
+ `-` so the name is filesystem-safe). A time filter can be implemented
31
+ by parsing the prefix and comparing it to `startTime` / `endTime`
32
+ without reading file contents.
33
+ - When the relevant time information lives **only inside files**, the
34
+ allowlist entry should err on the side of **not** being included —
35
+ unless the file is small, rarely changes, and its full contents are
36
+ acceptable to ship regardless of the requested window.
37
+
38
+ ## Rule 2 — Prefer conversation-filterable data
39
+
40
+ When the export request carries a `conversationId`, every allowlisted
41
+ subpath should narrow itself to that conversation **if at all possible**.
42
+
43
+ - Data that is intrinsically global (i.e. not associated with a single
44
+ conversation) is acceptable to include **only** when Rule 1 alone is
45
+ sufficient and the request has no `conversationId` filter.
46
+ - When a `conversationId` _is_ set and an entry cannot be scoped to it,
47
+ prefer omitting the entry for that particular export rather than
48
+ shipping unrelated conversation data.
49
+
50
+ The `<ISO-with-dashes>_<conversationId>` directory naming is again the
51
+ motivating example: the suffix lets us select exactly one
52
+ per-conversation directory without scanning file contents.
53
+
54
+ ## Rule 3 — Default deny
55
+
56
+ Anything in the workspace that is not explicitly added to the allowlist
57
+ module must remain excluded from the export archive. Adding a new entry
58
+ requires, in the same PR:
59
+
60
+ 1. Updating the allowlist module in this directory to teach it about the
61
+ new subpath (including its time filter, conversation filter, and
62
+ size cap).
63
+ 2. Updating this `AGENTS.md` to record the entry name, which filters it
64
+ honors, and its size cap under `## Allowlisted entries`.
65
+
66
+ Review must confirm both updates landed together. A workspace subpath
67
+ that is not mentioned in the registry below is, by definition, not
68
+ allowed in the export archive.
69
+
70
+ ## Rule 4 — Bounded size
71
+
72
+ Every allowlisted entry must enforce a byte cap so that a misbehaving
73
+ workspace (e.g. a runaway log, a giant attachment, a pathological skill)
74
+ cannot blow up the archive and defeat the export endpoint.
75
+
76
+ The current convention is **10 MB** across the workspace allowlist,
77
+ mirroring `MAX_LOG_PAYLOAD_BYTES` in `log-export-routes.ts`. Entries
78
+ should track the number of bytes already consumed and stop adding files
79
+ once the cap would be exceeded, preferring to include the newest /
80
+ most-relevant records first.
81
+
82
+ ## Allowlisted entries
83
+
84
+ - **`conversations/`**
85
+ - **Path**: `<workspace>/conversations/<ISO-with-dashes>_<conversationId>/`
86
+ - **Honors filters**: time (union of parsed `createdAt` prefix _and_
87
+ per-message `ts` inside `messages.jsonl`) and `conversationId`
88
+ (exact match on the directory-name suffix — no substring matching).
89
+ - **Time semantics**: A conversation directory is included if EITHER
90
+ its `createdAt` (parsed from the directory name) falls in the
91
+ requested window OR `messages.jsonl` contains at least one message
92
+ whose `ts` falls in the window. The cheap directory-name check runs
93
+ first; the per-message scan only runs as a fallback when the cheap
94
+ check failed, so the common in-window case stays IO-free. This is
95
+ the "support bundle" union — false positives are cheaper than false
96
+ negatives because the user almost always wants the conversations
97
+ they were _active in_ during the window, not just the ones they
98
+ _started_ during it.
99
+ - **Cap**: shares the 10 MB workspace cap defined by
100
+ `MAX_WORKSPACE_PAYLOAD_BYTES` in `workspace-allowlist.ts`.
101
+ - **Notes**: Directory names that don't match the canonical
102
+ `<ISO-with-dashes>_<conversationId>` format are silently skipped
103
+ (Rule 3 — default deny). Legacy `<id>_<ISO>` directories are
104
+ intentionally excluded until they migrate to the canonical format.
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Regression test for the `result.entries` contract on `collectWorkspaceData`.
3
+ *
4
+ * Consumers and telemetry rely on `collectWorkspaceData` always returning at
5
+ * least one entry summary for the `conversations` allowlist entry — even when
6
+ * something throws partway through the candidate loop. This file pins that
7
+ * contract by mocking `parseConversationDirName` to return a malicious object
8
+ * whose `createdAtMs` getter throws. That throw escapes the inner per-iteration
9
+ * try/catch (it happens during sort + filter expression evaluation, not inside
10
+ * the wrapped parser call), bubbles up to the outer try/catch in
11
+ * `collectWorkspaceData`, and verifies that `result.entries` still contains
12
+ * exactly one `conversations` entry summary.
13
+ *
14
+ * Lives in its own file because `mock.module` is a global module override and
15
+ * we don't want it bleeding into the rest of the workspace-allowlist tests.
16
+ */
17
+
18
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
21
+
22
+ mock.module("../../../../memory/conversation-directories.js", () => ({
23
+ parseConversationDirName: (_name: string) => {
24
+ // Return an object whose `createdAtMs` accessor throws. This bypasses the
25
+ // inner try/catch wrapping `parseConversationDirName(name)` (which only
26
+ // catches synchronous throws from the call itself, not from later property
27
+ // accesses) and triggers the unwrapped sort/filter comparisons further
28
+ // down in `collectConversations`.
29
+ return {
30
+ conversationId: "evil",
31
+ get createdAtMs(): number {
32
+ throw new Error("simulated parser corruption");
33
+ },
34
+ };
35
+ },
36
+ }));
37
+
38
+ import { getConversationsDir } from "../../../../util/platform.js";
39
+ import { collectWorkspaceData } from "../workspace-allowlist.js";
40
+
41
+ let staging: string;
42
+
43
+ beforeEach(() => {
44
+ // Fresh staging directory for each test.
45
+ const conversationsDir = getConversationsDir();
46
+ rmSync(conversationsDir, { recursive: true, force: true });
47
+ mkdirSync(conversationsDir, { recursive: true });
48
+
49
+ staging = join(
50
+ process.env.VELLUM_WORKSPACE_DIR ?? "/tmp",
51
+ "ws-allowlist-error-staging",
52
+ );
53
+ rmSync(staging, { recursive: true, force: true });
54
+ mkdirSync(staging, { recursive: true });
55
+
56
+ // Seed a single canonical-looking dir so the loop has something to chew on.
57
+ const dirName = "2025-01-15T00-00-00.000Z_conv-jan15";
58
+ const dir = join(conversationsDir, dirName);
59
+ mkdirSync(dir, { recursive: true });
60
+ writeFileSync(
61
+ join(dir, "meta.json"),
62
+ JSON.stringify({ name: dirName }),
63
+ "utf-8",
64
+ );
65
+ });
66
+
67
+ afterEach(() => {
68
+ try {
69
+ rmSync(staging, { recursive: true, force: true });
70
+ } catch {
71
+ /* best-effort cleanup */
72
+ }
73
+ try {
74
+ rmSync(getConversationsDir(), { recursive: true, force: true });
75
+ } catch {
76
+ /* best-effort cleanup */
77
+ }
78
+ });
79
+
80
+ describe("collectWorkspaceData — entry contract on unexpected error", () => {
81
+ test("synthesizes a conversations entry summary even when the loop throws", () => {
82
+ // The mocked parser returns a poisoned object whose `createdAtMs` accessor
83
+ // throws. The first read happens inside the time-filter checks in the
84
+ // candidate-collection loop, which is NOT wrapped in a per-iteration
85
+ // try/catch. The throw should propagate up to the outer try/catch in
86
+ // `collectWorkspaceData`, where it must be swallowed without dropping
87
+ // the entry summary.
88
+ const result = collectWorkspaceData({
89
+ staging,
90
+ // Force the time-filter branch to read `createdAtMs`.
91
+ startTime: 0,
92
+ });
93
+
94
+ // Contract: exactly one entry, named "conversations", regardless of error.
95
+ expect(result.entries).toHaveLength(1);
96
+ const [entry] = result.entries;
97
+ expect(entry.entry).toBe("conversations");
98
+ expect(entry.itemCount).toBe(0);
99
+ expect(entry.bytes).toBe(0);
100
+ expect(entry.skippedDueToCap).toBe(0);
101
+ expect(result.totalBytes).toBe(0);
102
+ });
103
+ });