@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
@@ -162,6 +162,7 @@ mock.module("../config/env.js", () => ({
162
162
  import { applyCanonicalGuardianDecision } from "../approvals/guardian-decision-primitive.js";
163
163
  import type { ActorContext } from "../approvals/guardian-request-resolvers.js";
164
164
  import { getResolver } from "../approvals/guardian-request-resolvers.js";
165
+ import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
165
166
  import type { TrustContext } from "../daemon/conversation-runtime-assembly.js";
166
167
  import {
167
168
  createCanonicalGuardianRequest,
@@ -169,6 +170,10 @@ import {
169
170
  listCanonicalGuardianRequests,
170
171
  updateCanonicalGuardianRequest,
171
172
  } from "../memory/canonical-guardian-store.js";
173
+ import {
174
+ createConversation,
175
+ updateConversationHostAccess,
176
+ } from "../memory/conversation-crud.js";
172
177
  import { getDb, initializeDb } from "../memory/db.js";
173
178
  import { scopedApprovalGrants } from "../memory/schema.js";
174
179
  import { bridgeConfirmationRequestToGuardian } from "../runtime/confirmation-request-guardian-bridge.js";
@@ -184,6 +189,8 @@ initializeDb();
184
189
  function resetTables(): void {
185
190
  const db = getDb();
186
191
  db.delete(scopedApprovalGrants).run();
192
+ db.run("DELETE FROM messages");
193
+ db.run("DELETE FROM conversations");
187
194
  db.run("DELETE FROM canonical_guardian_deliveries");
188
195
  db.run("DELETE FROM canonical_guardian_requests");
189
196
  }
@@ -246,6 +253,7 @@ describe("(a) target flow: trusted-contact inline guardian approval end-to-end",
246
253
  events.length = 0;
247
254
  emittedSignals.length = 0;
248
255
  deliveredReplies.length = 0;
256
+ _setOverridesForTesting({});
249
257
  mockGuardianBinding = {
250
258
  id: "binding-1",
251
259
  assistantId: "self",
@@ -1160,3 +1168,104 @@ describe("cross-milestone integration checks", () => {
1160
1168
  expect(freshReq?.followupState).toBeNull();
1161
1169
  });
1162
1170
  });
1171
+
1172
+ describe("(g) v2 trusted-contact flow: model-mediated guardian consent", () => {
1173
+ const handler = new ToolApprovalHandler({
1174
+ inlineGrantWait: { maxWaitMs: 100, intervalMs: 20 },
1175
+ });
1176
+
1177
+ beforeEach(() => {
1178
+ resetTables();
1179
+ events.length = 0;
1180
+ emittedSignals.length = 0;
1181
+ deliveredReplies.length = 0;
1182
+ _setOverridesForTesting({ "permission-controls-v2": true });
1183
+ mockGuardianBinding = {
1184
+ id: "binding-1",
1185
+ assistantId: "self",
1186
+ channel: "telegram",
1187
+ guardianExternalUserId: "guardian-1",
1188
+ guardianDeliveryChatId: "guardian-chat-1",
1189
+ guardianPrincipalId: "test-principal-id",
1190
+ status: "active",
1191
+ };
1192
+ });
1193
+
1194
+ test("trusted-contact host tools do not create tool_grant_request rows under v2", async () => {
1195
+ const conv = createConversation("trusted contact v2 host gate");
1196
+ const result = await handler.checkPreExecutionGates(
1197
+ "bash",
1198
+ { command: "ls" },
1199
+ makeToolContext({
1200
+ trustClass: "trusted_contact",
1201
+ conversationId: conv.id,
1202
+ }),
1203
+ "host",
1204
+ "high",
1205
+ Date.now(),
1206
+ emitLifecycleEvent,
1207
+ );
1208
+
1209
+ expect(result.allowed).toBe(false);
1210
+ if (result.allowed) return;
1211
+ expect(result.result.content).toContain(
1212
+ "computer access is not enabled for this conversation",
1213
+ );
1214
+
1215
+ const requests = listCanonicalGuardianRequests({
1216
+ kind: "tool_grant_request",
1217
+ status: "pending",
1218
+ });
1219
+ expect(requests).toHaveLength(0);
1220
+ expect(emittedSignals).toHaveLength(0);
1221
+ });
1222
+
1223
+ test("trusted-contact host tools run inline once the guardian has already enabled computer access for the conversation", async () => {
1224
+ const conv = createConversation("trusted contact v2 host access enabled");
1225
+ updateConversationHostAccess(conv.id, true);
1226
+
1227
+ const result = await handler.checkPreExecutionGates(
1228
+ "bash",
1229
+ { command: "ls" },
1230
+ makeToolContext({
1231
+ trustClass: "trusted_contact",
1232
+ conversationId: conv.id,
1233
+ }),
1234
+ "host",
1235
+ "high",
1236
+ Date.now(),
1237
+ emitLifecycleEvent,
1238
+ );
1239
+
1240
+ expect(result.allowed).toBe(true);
1241
+ });
1242
+
1243
+ test("confirmation_request guardian bridge is disabled for trusted contacts under v2", () => {
1244
+ const canonicalRequest = createCanonicalGuardianRequest({
1245
+ id: `req-v2-skip-${Date.now()}`,
1246
+ kind: "tool_approval",
1247
+ sourceType: "channel",
1248
+ sourceChannel: "telegram",
1249
+ conversationId: "conv-v2-skip",
1250
+ requesterExternalUserId: "requester-1",
1251
+ guardianExternalUserId: "guardian-1",
1252
+ guardianPrincipalId: "test-principal-id",
1253
+ toolName: "bash",
1254
+ status: "pending",
1255
+ expiresAt: Date.now() + 5 * 60_000,
1256
+ });
1257
+
1258
+ const result = bridgeConfirmationRequestToGuardian({
1259
+ canonicalRequest,
1260
+ trustContext: makeTrustedContactTrustContext(),
1261
+ conversationId: "conv-v2-skip",
1262
+ toolName: "bash",
1263
+ });
1264
+
1265
+ expect("skipped" in result && result.skipped).toBe(true);
1266
+ if ("skipped" in result) {
1267
+ expect(result.reason).toBe("v2_model_mediated");
1268
+ }
1269
+ expect(emittedSignals).toHaveLength(0);
1270
+ });
1271
+ });
@@ -0,0 +1,103 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
4
+ import type { ToolContext } from "../tools/types.js";
5
+
6
+ const hostAccessByConversation = new Map<string, boolean>();
7
+
8
+ mock.module("../memory/conversation-crud.js", () => ({
9
+ getConversationHostAccess: (conversationId: string) =>
10
+ hostAccessByConversation.get(conversationId) ?? false,
11
+ }));
12
+
13
+ function makeContext(overrides?: Partial<ToolContext>): ToolContext {
14
+ return {
15
+ workingDir: "/tmp/test",
16
+ conversationId: "test-conv",
17
+ trustClass: "guardian",
18
+ isInteractive: true,
19
+ ...overrides,
20
+ } as ToolContext;
21
+ }
22
+
23
+ beforeEach(() => {
24
+ _setOverridesForTesting({});
25
+ hostAccessByConversation.clear();
26
+ });
27
+
28
+ afterEach(() => {
29
+ _setOverridesForTesting({});
30
+ hostAccessByConversation.clear();
31
+ });
32
+
33
+ describe("v2-consent-policy", () => {
34
+ test("returns legacy when the flag is disabled", async () => {
35
+ const { evaluateV2ConsentDisposition } =
36
+ await import("../permissions/v2-consent-policy.js");
37
+
38
+ expect(evaluateV2ConsentDisposition("host_bash", {}, makeContext())).toBe(
39
+ "legacy",
40
+ );
41
+ });
42
+
43
+ test("auto-allows non-host tools when the flag is enabled", async () => {
44
+ _setOverridesForTesting({ "permission-controls-v2": true });
45
+ const { evaluateV2ConsentDisposition } =
46
+ await import("../permissions/v2-consent-policy.js");
47
+
48
+ expect(evaluateV2ConsentDisposition("bash", {}, makeContext())).toBe(
49
+ "auto_allow",
50
+ );
51
+ });
52
+
53
+ test("uses conversation-scoped host access when the flag is enabled", async () => {
54
+ _setOverridesForTesting({ "permission-controls-v2": true });
55
+ hostAccessByConversation.set("allowed-conv", true);
56
+ hostAccessByConversation.set("blocked-conv", false);
57
+ const { evaluateV2ConsentDisposition } =
58
+ await import("../permissions/v2-consent-policy.js");
59
+
60
+ expect(
61
+ evaluateV2ConsentDisposition(
62
+ "host_bash",
63
+ {},
64
+ makeContext({ conversationId: "allowed-conv" }),
65
+ ),
66
+ ).toBe("auto_allow");
67
+ expect(
68
+ evaluateV2ConsentDisposition(
69
+ "host_bash",
70
+ {},
71
+ makeContext({ conversationId: "blocked-conv" }),
72
+ ),
73
+ ).toBe("prompt_host_access");
74
+ });
75
+
76
+ test("host-access prompts are identified by the stripped-down prompt shape", async () => {
77
+ _setOverridesForTesting({ "permission-controls-v2": true });
78
+ const {
79
+ CONVERSATION_HOST_ACCESS_PROMPT,
80
+ isConversationHostAccessEnablePrompt,
81
+ } = await import("../permissions/v2-consent-policy.js");
82
+
83
+ expect(
84
+ isConversationHostAccessEnablePrompt({
85
+ toolName: "host_bash",
86
+ ...CONVERSATION_HOST_ACCESS_PROMPT,
87
+ }),
88
+ ).toBe(true);
89
+ expect(
90
+ isConversationHostAccessEnablePrompt({
91
+ toolName: "host_bash",
92
+ ...CONVERSATION_HOST_ACCESS_PROMPT,
93
+ allowlistOptions: [
94
+ {
95
+ label: "Specific command",
96
+ description: "legacy rule",
97
+ pattern: "bash:*",
98
+ },
99
+ ],
100
+ }),
101
+ ).toBe(false);
102
+ });
103
+ });
@@ -0,0 +1,168 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
+
12
+ import { seedPkbAutoinjectMigration } from "../workspace/migrations/030-seed-pkb-autoinject.js";
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Helpers
16
+ // ---------------------------------------------------------------------------
17
+
18
+ let workspaceDir: string;
19
+ let pkbDir: string;
20
+
21
+ function freshWorkspace(): void {
22
+ workspaceDir = join(
23
+ tmpdir(),
24
+ `vellum-migration-030-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
25
+ );
26
+ pkbDir = join(workspaceDir, "pkb");
27
+ mkdirSync(pkbDir, { recursive: true });
28
+ }
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Setup / Teardown
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const dirs: string[] = [];
35
+
36
+ beforeEach(() => {
37
+ freshWorkspace();
38
+ dirs.push(workspaceDir);
39
+ });
40
+
41
+ afterEach(() => {
42
+ for (const dir of dirs.splice(0)) {
43
+ rmSync(dir, { recursive: true, force: true });
44
+ }
45
+ });
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Tests
49
+ // ---------------------------------------------------------------------------
50
+
51
+ describe("030-seed-pkb-autoinject migration", () => {
52
+ test("has correct migration id", () => {
53
+ expect(seedPkbAutoinjectMigration.id).toBe("030-seed-pkb-autoinject");
54
+ });
55
+
56
+ // ─── run() ──────────────────────────────────────────────────────────────
57
+
58
+ test("creates _autoinject.md with default content", () => {
59
+ seedPkbAutoinjectMigration.run(workspaceDir);
60
+
61
+ const filePath = join(pkbDir, "_autoinject.md");
62
+ expect(existsSync(filePath)).toBe(true);
63
+
64
+ const content = readFileSync(filePath, "utf-8");
65
+ expect(content).toContain("INDEX.md");
66
+ expect(content).toContain("essentials.md");
67
+ expect(content).toContain("threads.md");
68
+ expect(content).toContain("buffer.md");
69
+ });
70
+
71
+ test("no-op when pkb/ does not exist", () => {
72
+ rmSync(pkbDir, { recursive: true, force: true });
73
+ seedPkbAutoinjectMigration.run(workspaceDir);
74
+ expect(existsSync(join(pkbDir, "_autoinject.md"))).toBe(false);
75
+ });
76
+
77
+ test("idempotent — does not overwrite existing _autoinject.md", () => {
78
+ const customContent = "INDEX.md\ncustom-topic.md\n";
79
+ writeFileSync(join(pkbDir, "_autoinject.md"), customContent, "utf-8");
80
+
81
+ seedPkbAutoinjectMigration.run(workspaceDir);
82
+
83
+ const content = readFileSync(join(pkbDir, "_autoinject.md"), "utf-8");
84
+ expect(content).toBe(customContent);
85
+ });
86
+
87
+ test("appends _autoinject.md entry to INDEX.md", () => {
88
+ const indexContent =
89
+ "# Knowledge Base\n\n## Always Loaded\n" +
90
+ "- essentials.md — Core facts\n" +
91
+ "- threads.md — Active threads\n" +
92
+ "- buffer.md — Inbox\n\n" +
93
+ "## Topics\n";
94
+ writeFileSync(join(pkbDir, "INDEX.md"), indexContent, "utf-8");
95
+
96
+ seedPkbAutoinjectMigration.run(workspaceDir);
97
+
98
+ const updated = readFileSync(join(pkbDir, "INDEX.md"), "utf-8");
99
+ expect(updated).toContain("_autoinject.md");
100
+ // Should appear after buffer.md
101
+ const bufferIdx = updated.indexOf("buffer.md");
102
+ const autoinjectIdx = updated.indexOf("_autoinject.md");
103
+ expect(autoinjectIdx).toBeGreaterThan(bufferIdx);
104
+ });
105
+
106
+ test("does not duplicate _autoinject.md entry in INDEX.md", () => {
107
+ const indexContent =
108
+ "# Knowledge Base\n\n## Always Loaded\n" +
109
+ "- buffer.md — Inbox\n" +
110
+ "- _autoinject.md — Controls autoinjection\n\n" +
111
+ "## Topics\n";
112
+ writeFileSync(join(pkbDir, "INDEX.md"), indexContent, "utf-8");
113
+
114
+ seedPkbAutoinjectMigration.run(workspaceDir);
115
+
116
+ const updated = readFileSync(join(pkbDir, "INDEX.md"), "utf-8");
117
+ const matches = updated.match(/_autoinject\.md/g);
118
+ expect(matches?.length).toBe(1);
119
+ });
120
+
121
+ test("handles missing INDEX.md gracefully", () => {
122
+ // No INDEX.md — should still create _autoinject.md without error
123
+ seedPkbAutoinjectMigration.run(workspaceDir);
124
+ expect(existsSync(join(pkbDir, "_autoinject.md"))).toBe(true);
125
+ });
126
+
127
+ // ─── down() ─────────────────────────────────────────────────────────────
128
+
129
+ describe("down()", () => {
130
+ test("removes _autoinject.md when content matches template", () => {
131
+ seedPkbAutoinjectMigration.run(workspaceDir);
132
+ expect(existsSync(join(pkbDir, "_autoinject.md"))).toBe(true);
133
+
134
+ seedPkbAutoinjectMigration.down(workspaceDir);
135
+ expect(existsSync(join(pkbDir, "_autoinject.md"))).toBe(false);
136
+ });
137
+
138
+ test("preserves _autoinject.md when user has customized it", () => {
139
+ const customContent = "INDEX.md\nmy-custom-file.md\n";
140
+ writeFileSync(join(pkbDir, "_autoinject.md"), customContent, "utf-8");
141
+
142
+ seedPkbAutoinjectMigration.down(workspaceDir);
143
+
144
+ expect(existsSync(join(pkbDir, "_autoinject.md"))).toBe(true);
145
+ expect(readFileSync(join(pkbDir, "_autoinject.md"), "utf-8")).toBe(
146
+ customContent,
147
+ );
148
+ });
149
+
150
+ test("no-op when _autoinject.md does not exist", () => {
151
+ seedPkbAutoinjectMigration.down(workspaceDir);
152
+ // Should not throw
153
+ });
154
+
155
+ test("no-op when pkb/ does not exist", () => {
156
+ rmSync(pkbDir, { recursive: true, force: true });
157
+ seedPkbAutoinjectMigration.down(workspaceDir);
158
+ // Should not throw
159
+ });
160
+
161
+ test("idempotent — calling down() twice is safe", () => {
162
+ seedPkbAutoinjectMigration.run(workspaceDir);
163
+ seedPkbAutoinjectMigration.down(workspaceDir);
164
+ seedPkbAutoinjectMigration.down(workspaceDir);
165
+ // Should not throw
166
+ });
167
+ });
168
+ });
@@ -198,7 +198,7 @@ describe("isWorkspaceScopedInvocation", () => {
198
198
  // ── Bash ───────────────────────────────────────────────────────────
199
199
 
200
200
  describe("bash", () => {
201
- test("returns true (sandbox handles isolation)", () => {
201
+ test("returns true (container handles isolation)", () => {
202
202
  expect(
203
203
  isWorkspaceScopedInvocation(
204
204
  "bash",
@@ -255,12 +255,7 @@ describe("isWorkspaceScopedInvocation", () => {
255
255
  // ── Always-scoped safe tools ───────────────────────────────────────
256
256
 
257
257
  describe("always-scoped tools", () => {
258
- const safeTools = [
259
- "skill_load",
260
- "recall",
261
- "ui_update",
262
- "ui_dismiss",
263
- ];
258
+ const safeTools = ["skill_load", "recall", "ui_update", "ui_dismiss"];
264
259
 
265
260
  for (const tool of safeTools) {
266
261
  test(`${tool} is workspace-scoped`, () => {
@@ -32,6 +32,7 @@ import type {
32
32
 
33
33
  import type { ServerMessage } from "../daemon/message-protocol.js";
34
34
  import type { UserDecision } from "../permissions/types.js";
35
+ import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
35
36
  import * as pendingInteractions from "../runtime/pending-interactions.js";
36
37
  import { getLogger } from "../util/logger.js";
37
38
 
@@ -184,6 +185,15 @@ export class VellumAcpClientHandler implements Client {
184
185
  kind: opt.kind,
185
186
  }));
186
187
 
188
+ if (isPermissionControlsV2Enabled()) {
189
+ const allowOptionId = findAllowOptionId(options);
190
+ return {
191
+ outcome: allowOptionId
192
+ ? { outcome: "selected", optionId: allowOptionId }
193
+ : { outcome: "cancelled" },
194
+ };
195
+ }
196
+
187
197
  // Send the confirmation_request first — this triggers makeEventSender
188
198
  // which registers a normal "confirmation" entry in pendingInteractions.
189
199
  this.sendToVellum({
@@ -422,15 +432,31 @@ function mapDecisionToOptionId(
422
432
  const alwaysDeny = options.find((o) => o.kind === "reject_always");
423
433
  if (alwaysDeny) return alwaysDeny.optionId;
424
434
  }
425
- const denyOpt =
426
- options.find((o) => o.kind === "reject_once") ??
427
- options.find((o) => o.kind === "reject_always");
428
- if (denyOpt) return denyOpt.optionId;
435
+ const denyOpt = findRejectOptionId(options);
436
+ if (denyOpt) return denyOpt;
429
437
 
430
438
  // Fallback: return first option
431
439
  return options[0]?.optionId ?? "deny";
432
440
  }
433
441
 
442
+ function findRejectOptionId(
443
+ options: Array<{ optionId: string; kind: string }>,
444
+ ): string | undefined {
445
+ return (
446
+ options.find((o) => o.kind === "reject_once")?.optionId ??
447
+ options.find((o) => o.kind === "reject_always")?.optionId
448
+ );
449
+ }
450
+
451
+ function findAllowOptionId(
452
+ options: Array<{ optionId: string; kind: string }>,
453
+ ): string | undefined {
454
+ return (
455
+ options.find((o) => o.kind === "allow_once")?.optionId ??
456
+ options.find((o) => o.kind === "allow_always")?.optionId
457
+ );
458
+ }
459
+
434
460
  /**
435
461
  * Extracts text from a ContentBlock.
436
462
  */
package/src/agent/loop.ts CHANGED
@@ -82,7 +82,12 @@ export type AgentEvent =
82
82
  toolUseId: string;
83
83
  input: Record<string, unknown>;
84
84
  }
85
- | { type: "server_tool_complete"; toolUseId: string; isError: boolean }
85
+ | {
86
+ type: "server_tool_complete";
87
+ toolUseId: string;
88
+ isError: boolean;
89
+ content?: unknown[];
90
+ }
86
91
  | { type: "error"; error: Error }
87
92
  | {
88
93
  type: "usage";
@@ -98,7 +103,7 @@ export type AgentEvent =
98
103
  };
99
104
 
100
105
  const DEFAULT_CONFIG: AgentLoopConfig = {
101
- maxTokens: 16000,
106
+ maxTokens: 64000,
102
107
  effort: "high",
103
108
  minTurnIntervalMs: 150,
104
109
  };
@@ -201,7 +206,6 @@ export class AgentLoop {
201
206
  ): Promise<Message[]> {
202
207
  const history = [...messages];
203
208
  let toolUseTurns = 0;
204
- let nudgedForEmptyResponse = false;
205
209
  let consecutiveErrorTurns = 0;
206
210
  let lastLlmCallTime = 0;
207
211
  const rlog = requestId ? log.child({ requestId }) : log;
@@ -345,6 +349,7 @@ export class AgentLoop {
345
349
  type: "server_tool_complete",
346
350
  toolUseId: event.toolUseId,
347
351
  isError: event.isError,
352
+ ...(event.content ? { content: event.content } : {}),
348
353
  });
349
354
  }
350
355
  },
@@ -403,35 +408,7 @@ export class AgentLoop {
403
408
  block.type === "tool_use",
404
409
  );
405
410
 
406
- // Check if the assistant turn contained any visible text (used for
407
- // the empty-response nudge).
408
- const hasTextBlock = response.content.some(
409
- (block) => block.type === "text" && block.text.trim().length > 0,
410
- );
411
-
412
411
  if (toolUseBlocks.length === 0 || !this.toolExecutor) {
413
- // Check if the LLM returned no text after tool results — nudge it to respond
414
- const lastUserMsg =
415
- history.length >= 2 ? history[history.length - 2] : undefined;
416
- const lastWasToolResult =
417
- lastUserMsg?.role === "user" &&
418
- lastUserMsg.content.some((block) => block.type === "tool_result");
419
-
420
- if (!hasTextBlock && lastWasToolResult && !nudgedForEmptyResponse) {
421
- nudgedForEmptyResponse = true;
422
- history.push({
423
- role: "user",
424
- content: [
425
- {
426
- type: "text",
427
- text: "<system_notice>You executed tools but didn't tell the user what happened. Provide a brief, conversational summary of the results.</system_notice>",
428
- },
429
- ],
430
- });
431
- continue;
432
- }
433
-
434
- // No tool calls or no executor — done
435
412
  break;
436
413
  }
437
414
 
@@ -738,10 +715,10 @@ export function compactAxTreeHistory(messages: Message[]): Message[] {
738
715
  * once by the LLM on the turn it was captured, then replaced with a text
739
716
  * placeholder on subsequent turns.
740
717
  *
741
- * We look for the last user message with tool_results (not just the last user
742
- * message) because the empty-response nudge path appends a text-only user
743
- * message after the tool results. Preserving images in that case ensures
744
- * the model still sees the screenshot on the retry.
718
+ * We target the last user message with tool_results (not just the last user
719
+ * message) because a plain-text user message may follow the tool-result
720
+ * turn. Using the last user message unconditionally would leave the most
721
+ * recent tool screenshots unprotected from stripping.
745
722
  */
746
723
  function stripOldImageBlocks(history: Message[]): Message[] {
747
724
  // Find the last user message that contains tool_result blocks.
@@ -26,6 +26,7 @@ import {
26
26
  } from "../notifications/signal.js";
27
27
  import { addRule } from "../permissions/trust-store.js";
28
28
  import type { UserDecision } from "../permissions/types.js";
29
+ import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
29
30
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
30
31
  import { mintDaemonDeliveryToken } from "../runtime/auth/token-service.js";
31
32
  import type { ApprovalAction } from "../runtime/channel-approval-types.js";
@@ -192,8 +193,9 @@ const pendingInteractionResolver: GuardianRequestResolver = {
192
193
  // Handle approve_always: persist a trust rule when the confirmation
193
194
  // explicitly allows persistence and provides explicit options.
194
195
  if (
195
- decision.action === "approve_always" ||
196
- decision.action === "approve_once"
196
+ !isPermissionControlsV2Enabled() &&
197
+ (decision.action === "approve_always" ||
198
+ decision.action === "approve_once")
197
199
  ) {
198
200
  const details = interaction.confirmationDetails;
199
201
  if (
@@ -237,19 +239,23 @@ const pendingInteractionResolver: GuardianRequestResolver = {
237
239
  // temporal modes (approve_10m, approve_conversation) reach the session with
238
240
  // the correct UserDecision instead of collapsing to plain "allow".
239
241
  let userDecision: UserDecision;
240
- switch (decision.action) {
241
- case "reject":
242
- userDecision = "deny";
243
- break;
244
- case "approve_10m":
245
- userDecision = "allow_10m";
246
- break;
247
- case "approve_conversation":
248
- userDecision = "allow_conversation";
249
- break;
250
- default:
251
- userDecision = "allow";
252
- break;
242
+ if (isPermissionControlsV2Enabled()) {
243
+ userDecision = decision.action === "reject" ? "deny" : "allow";
244
+ } else {
245
+ switch (decision.action) {
246
+ case "reject":
247
+ userDecision = "deny";
248
+ break;
249
+ case "approve_10m":
250
+ userDecision = "allow_10m";
251
+ break;
252
+ case "approve_conversation":
253
+ userDecision = "allow_conversation";
254
+ break;
255
+ default:
256
+ userDecision = "allow";
257
+ break;
258
+ }
253
259
  }
254
260
  resolved.conversation!.handleConfirmationResponse(
255
261
  request.id,