@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
@@ -0,0 +1,411 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mock } from "bun:test";
3
+
4
+ // Mock conversation-crud before importing tool executors that depend on it.
5
+ mock.module("../memory/conversation-crud.js", () => ({
6
+ getConversationType: () => "default",
7
+ setConversationOriginChannelIfUnset: () => {},
8
+ updateConversationContextWindow: () => {},
9
+ deleteMessageById: () => {},
10
+ updateConversationTitle: () => {},
11
+ updateConversationUsage: () => {},
12
+ addMessage: () => ({ id: "mock-msg-id" }),
13
+ getConversation: () => ({
14
+ id: "conv-1",
15
+ contextSummary: null,
16
+ contextCompactedMessageCount: 0,
17
+ totalInputTokens: 0,
18
+ totalOutputTokens: 0,
19
+ totalEstimatedCost: 0,
20
+ title: null,
21
+ }),
22
+ provenanceFromTrustContext: () => ({
23
+ source: "user",
24
+ trustContext: undefined,
25
+ }),
26
+ getConversationOriginInterface: () => null,
27
+ getConversationOriginChannel: () => null,
28
+ getMessages: () => null,
29
+ createConversation: () => ({ id: "mock-conv" }),
30
+ }));
31
+
32
+ import type { Message } from "../providers/types.js";
33
+ import { getSubagentManager } from "../subagent/index.js";
34
+ import { executeSubagentSpawn } from "../tools/subagent/spawn.js";
35
+
36
+ // ── Shared helpers ──────────────────────────────────────────────────
37
+
38
+ function makeContext(
39
+ conversationId: string,
40
+ extras: Record<string, unknown> = {},
41
+ ) {
42
+ return {
43
+ workingDir: "/tmp",
44
+ conversationId,
45
+ trustClass: "guardian" as const,
46
+ ...extras,
47
+ } as import("../tools/types.js").ToolContext;
48
+ }
49
+
50
+ const FAKE_PARENT_MESSAGES: Message[] = [
51
+ {
52
+ role: "user",
53
+ content: [{ type: "text", text: "Hello from parent" }],
54
+ },
55
+ {
56
+ role: "assistant",
57
+ content: [{ type: "text", text: "Hello! How can I help?" }],
58
+ },
59
+ ];
60
+
61
+ describe("subagent_spawn fork parameter", () => {
62
+ test("fork: true passes parent context to manager", async () => {
63
+ const manager = getSubagentManager();
64
+ const originalSpawn = manager.spawn.bind(manager);
65
+ const originalResolve = manager.resolveParentConversation;
66
+
67
+ let capturedConfig: Record<string, unknown> | undefined;
68
+ manager.spawn = async (config: Record<string, unknown>) => {
69
+ capturedConfig = config;
70
+ return "fork-subagent-id";
71
+ };
72
+
73
+ // Wire resolveParentConversation to return a fake parent conversation.
74
+ manager.resolveParentConversation = (id: string) => {
75
+ if (id === "parent-conv-1") {
76
+ return {
77
+ messages: FAKE_PARENT_MESSAGES,
78
+ getCurrentSystemPrompt: () => "You are a helpful assistant.",
79
+ } as any;
80
+ }
81
+ return undefined;
82
+ };
83
+
84
+ try {
85
+ const result = await executeSubagentSpawn(
86
+ {
87
+ label: "Fork task",
88
+ objective: "Summarize our discussion",
89
+ fork: true,
90
+ },
91
+ makeContext("parent-conv-1", { sendToClient: () => {} }),
92
+ );
93
+
94
+ expect(result.isError).toBe(false);
95
+ expect(capturedConfig).toBeDefined();
96
+ expect(capturedConfig!.fork).toBe(true);
97
+ expect(capturedConfig!.parentMessages).toEqual(FAKE_PARENT_MESSAGES);
98
+ expect(capturedConfig!.parentSystemPrompt).toBe(
99
+ "You are a helpful assistant.",
100
+ );
101
+ expect(capturedConfig!.parentConversationId).toBe("parent-conv-1");
102
+
103
+ // Verify the response includes isFork
104
+ const parsed = JSON.parse(result.content);
105
+ expect(parsed.isFork).toBe(true);
106
+ expect(parsed.subagentId).toBe("fork-subagent-id");
107
+ expect(parsed.status).toBe("pending");
108
+ expect(parsed.message).toContain("Forked subagent");
109
+ } finally {
110
+ manager.spawn = originalSpawn;
111
+ manager.resolveParentConversation = originalResolve;
112
+ }
113
+ });
114
+
115
+ test("fork: true ignores role parameter", async () => {
116
+ const manager = getSubagentManager();
117
+ const originalSpawn = manager.spawn.bind(manager);
118
+ const originalResolve = manager.resolveParentConversation;
119
+
120
+ let capturedConfig: Record<string, unknown> | undefined;
121
+ manager.spawn = async (config: Record<string, unknown>) => {
122
+ capturedConfig = config;
123
+ return "fork-role-id";
124
+ };
125
+
126
+ manager.resolveParentConversation = (id: string) => {
127
+ if (id === "parent-conv-role") {
128
+ return {
129
+ messages: FAKE_PARENT_MESSAGES,
130
+ getCurrentSystemPrompt: () => "Parent prompt.",
131
+ } as any;
132
+ }
133
+ return undefined;
134
+ };
135
+
136
+ try {
137
+ const result = await executeSubagentSpawn(
138
+ {
139
+ label: "Fork with role",
140
+ objective: "Do something",
141
+ fork: true,
142
+ role: "researcher", // should be ignored
143
+ },
144
+ makeContext("parent-conv-role", { sendToClient: () => {} }),
145
+ );
146
+
147
+ expect(result.isError).toBe(false);
148
+ expect(capturedConfig).toBeDefined();
149
+ // When fork is true, role should NOT be passed to the manager config
150
+ expect(capturedConfig!.role).toBeUndefined();
151
+ expect(capturedConfig!.fork).toBe(true);
152
+ } finally {
153
+ manager.spawn = originalSpawn;
154
+ manager.resolveParentConversation = originalResolve;
155
+ }
156
+ });
157
+
158
+ test("fork: true defaults sendResultToUser to false", async () => {
159
+ const manager = getSubagentManager();
160
+ const originalSpawn = manager.spawn.bind(manager);
161
+ const originalResolve = manager.resolveParentConversation;
162
+
163
+ let capturedConfig: Record<string, unknown> | undefined;
164
+ manager.spawn = async (config: Record<string, unknown>) => {
165
+ capturedConfig = config;
166
+ return "fork-silent-id";
167
+ };
168
+
169
+ manager.resolveParentConversation = (id: string) => {
170
+ if (id === "parent-conv-silent") {
171
+ return {
172
+ messages: FAKE_PARENT_MESSAGES,
173
+ getCurrentSystemPrompt: () => "Parent prompt.",
174
+ } as any;
175
+ }
176
+ return undefined;
177
+ };
178
+
179
+ try {
180
+ // No send_result_to_user specified — fork should default to false
181
+ const result = await executeSubagentSpawn(
182
+ {
183
+ label: "Silent fork",
184
+ objective: "Internal processing",
185
+ fork: true,
186
+ },
187
+ makeContext("parent-conv-silent", { sendToClient: () => {} }),
188
+ );
189
+
190
+ expect(result.isError).toBe(false);
191
+ expect(capturedConfig).toBeDefined();
192
+ expect(capturedConfig!.sendResultToUser).toBe(false);
193
+ } finally {
194
+ manager.spawn = originalSpawn;
195
+ manager.resolveParentConversation = originalResolve;
196
+ }
197
+ });
198
+
199
+ test("fork: true with explicit send_result_to_user: true preserves it", async () => {
200
+ const manager = getSubagentManager();
201
+ const originalSpawn = manager.spawn.bind(manager);
202
+ const originalResolve = manager.resolveParentConversation;
203
+
204
+ let capturedConfig: Record<string, unknown> | undefined;
205
+ manager.spawn = async (config: Record<string, unknown>) => {
206
+ capturedConfig = config;
207
+ return "fork-visible-id";
208
+ };
209
+
210
+ manager.resolveParentConversation = (id: string) => {
211
+ if (id === "parent-conv-visible") {
212
+ return {
213
+ messages: FAKE_PARENT_MESSAGES,
214
+ getCurrentSystemPrompt: () => "Parent prompt.",
215
+ } as any;
216
+ }
217
+ return undefined;
218
+ };
219
+
220
+ try {
221
+ const result = await executeSubagentSpawn(
222
+ {
223
+ label: "Visible fork",
224
+ objective: "Share with user",
225
+ fork: true,
226
+ send_result_to_user: true,
227
+ },
228
+ makeContext("parent-conv-visible", { sendToClient: () => {} }),
229
+ );
230
+
231
+ expect(result.isError).toBe(false);
232
+ expect(capturedConfig).toBeDefined();
233
+ expect(capturedConfig!.sendResultToUser).toBe(true);
234
+ } finally {
235
+ manager.spawn = originalSpawn;
236
+ manager.resolveParentConversation = originalResolve;
237
+ }
238
+ });
239
+
240
+ test("fork: false / omitted behaves identically to current behavior", async () => {
241
+ const manager = getSubagentManager();
242
+ const originalSpawn = manager.spawn.bind(manager);
243
+
244
+ // Test with fork: false
245
+ let capturedConfig: Record<string, unknown> | undefined;
246
+ manager.spawn = async (config: Record<string, unknown>) => {
247
+ capturedConfig = config;
248
+ return "regular-subagent-id";
249
+ };
250
+
251
+ try {
252
+ const result = await executeSubagentSpawn(
253
+ {
254
+ label: "Regular task",
255
+ objective: "Do something",
256
+ fork: false,
257
+ role: "researcher",
258
+ context: "Some context",
259
+ },
260
+ makeContext("regular-conv-1", { sendToClient: () => {} }),
261
+ );
262
+
263
+ expect(result.isError).toBe(false);
264
+ expect(capturedConfig).toBeDefined();
265
+ // Should NOT have fork fields
266
+ expect(capturedConfig!.fork).toBeUndefined();
267
+ expect(capturedConfig!.parentMessages).toBeUndefined();
268
+ expect(capturedConfig!.parentSystemPrompt).toBeUndefined();
269
+ // Should have role
270
+ expect(capturedConfig!.role).toBe("researcher");
271
+ // Should have regular sendResultToUser default (true)
272
+ expect(capturedConfig!.sendResultToUser).toBe(true);
273
+ expect(capturedConfig!.context).toBe("Some context");
274
+
275
+ // Response should NOT include isFork
276
+ const parsed = JSON.parse(result.content);
277
+ expect(parsed.isFork).toBeUndefined();
278
+ expect(parsed.message).toContain("spawned");
279
+ expect(parsed.message).not.toContain("Forked");
280
+ } finally {
281
+ manager.spawn = originalSpawn;
282
+ }
283
+ });
284
+
285
+ test("fork omitted behaves like fork: false", async () => {
286
+ const manager = getSubagentManager();
287
+ const originalSpawn = manager.spawn.bind(manager);
288
+
289
+ let capturedConfig: Record<string, unknown> | undefined;
290
+ manager.spawn = async (config: Record<string, unknown>) => {
291
+ capturedConfig = config;
292
+ return "omitted-fork-id";
293
+ };
294
+
295
+ try {
296
+ const result = await executeSubagentSpawn(
297
+ {
298
+ label: "No fork field",
299
+ objective: "Standard task",
300
+ },
301
+ makeContext("no-fork-conv", { sendToClient: () => {} }),
302
+ );
303
+
304
+ expect(result.isError).toBe(false);
305
+ expect(capturedConfig).toBeDefined();
306
+ expect(capturedConfig!.fork).toBeUndefined();
307
+ expect(capturedConfig!.parentMessages).toBeUndefined();
308
+ expect(capturedConfig!.parentSystemPrompt).toBeUndefined();
309
+ expect(capturedConfig!.sendResultToUser).toBe(true);
310
+
311
+ const parsed = JSON.parse(result.content);
312
+ expect(parsed.isFork).toBeUndefined();
313
+ } finally {
314
+ manager.spawn = originalSpawn;
315
+ }
316
+ });
317
+
318
+ test("error when parent conversation cannot be resolved", async () => {
319
+ const manager = getSubagentManager();
320
+ const originalResolve = manager.resolveParentConversation;
321
+
322
+ // resolveParentConversation returns undefined
323
+ manager.resolveParentConversation = () => undefined;
324
+
325
+ try {
326
+ const result = await executeSubagentSpawn(
327
+ {
328
+ label: "Orphan fork",
329
+ objective: "Should fail",
330
+ fork: true,
331
+ },
332
+ makeContext("nonexistent-parent", { sendToClient: () => {} }),
333
+ );
334
+
335
+ expect(result.isError).toBe(true);
336
+ expect(result.content).toContain("Cannot fork");
337
+ expect(result.content).toContain("parent conversation could not be resolved");
338
+ } finally {
339
+ manager.resolveParentConversation = originalResolve;
340
+ }
341
+ });
342
+
343
+ test("error when resolveParentConversation is not wired", async () => {
344
+ const manager = getSubagentManager();
345
+ const originalResolve = manager.resolveParentConversation;
346
+
347
+ // Unset the callback entirely
348
+ manager.resolveParentConversation = undefined;
349
+
350
+ try {
351
+ const result = await executeSubagentSpawn(
352
+ {
353
+ label: "Unwired fork",
354
+ objective: "Should fail",
355
+ fork: true,
356
+ },
357
+ makeContext("some-parent", { sendToClient: () => {} }),
358
+ );
359
+
360
+ expect(result.isError).toBe(true);
361
+ expect(result.content).toContain("Cannot fork");
362
+ expect(result.content).toContain("parent conversation could not be resolved");
363
+ } finally {
364
+ manager.resolveParentConversation = originalResolve;
365
+ }
366
+ });
367
+
368
+ test("fork: true shallow copies parent messages", async () => {
369
+ const manager = getSubagentManager();
370
+ const originalSpawn = manager.spawn.bind(manager);
371
+ const originalResolve = manager.resolveParentConversation;
372
+
373
+ const originalMessages = [...FAKE_PARENT_MESSAGES];
374
+ let capturedConfig: Record<string, unknown> | undefined;
375
+ manager.spawn = async (config: Record<string, unknown>) => {
376
+ capturedConfig = config;
377
+ return "copy-check-id";
378
+ };
379
+
380
+ manager.resolveParentConversation = (id: string) => {
381
+ if (id === "parent-conv-copy") {
382
+ return {
383
+ messages: originalMessages,
384
+ getCurrentSystemPrompt: () => "Prompt.",
385
+ } as any;
386
+ }
387
+ return undefined;
388
+ };
389
+
390
+ try {
391
+ await executeSubagentSpawn(
392
+ {
393
+ label: "Copy check",
394
+ objective: "Test",
395
+ fork: true,
396
+ },
397
+ makeContext("parent-conv-copy", { sendToClient: () => {} }),
398
+ );
399
+
400
+ expect(capturedConfig).toBeDefined();
401
+ const passedMessages = capturedConfig!.parentMessages as Message[];
402
+ // Should be a different array reference (shallow copy via spread)
403
+ expect(passedMessages).not.toBe(originalMessages);
404
+ // But same content
405
+ expect(passedMessages).toEqual(originalMessages);
406
+ } finally {
407
+ manager.spawn = originalSpawn;
408
+ manager.resolveParentConversation = originalResolve;
409
+ }
410
+ });
411
+ });
@@ -86,6 +86,7 @@ function injectSubagent(
86
86
  },
87
87
  status,
88
88
  conversationId: `conv-${subagentId}`,
89
+ isFork: false,
89
90
  createdAt: Date.now(),
90
91
  usage: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
91
92
  ...overrides,
@@ -68,6 +68,7 @@ describe("SubagentState type shape", () => {
68
68
  },
69
69
  status: "pending" as SubagentStatus,
70
70
  conversationId: "conv-id",
71
+ isFork: false,
71
72
  createdAt: Date.now(),
72
73
  usage: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
73
74
  };
@@ -1,16 +1,15 @@
1
- /**
2
- * Tests for the "Action Confirmation Mode" system prompt injection.
3
- *
4
- * Verifies:
5
- * - Prompt includes the section when `permission-controls-v2` flag is enabled
6
- * AND `askBeforeActing` is `true`.
7
- * - Prompt excludes the section when the flag is disabled.
8
- * - Prompt excludes the section when `askBeforeActing` is `false`.
9
- */
10
- import { mkdirSync } from "node:fs";
11
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
1
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import {
4
+ afterAll,
5
+ afterEach,
6
+ beforeEach,
7
+ describe,
8
+ expect,
9
+ mock,
10
+ test,
11
+ } from "bun:test";
12
12
 
13
- // Mock platform to use a temp directory
14
13
  const TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
15
14
 
16
15
  const noopLogger: Record<string, unknown> = new Proxy(
@@ -65,75 +64,32 @@ mock.module("../prompts/user-reference.js", () => ({
65
64
  resolveUserPronouns: () => null,
66
65
  }));
67
66
 
68
- // ---------------------------------------------------------------------------
69
- // Controllable mocks for feature flags and permission mode
70
- // ---------------------------------------------------------------------------
71
-
72
- let flagEnabled = false;
73
- let askBeforeActing = true;
74
-
75
- mock.module("../config/assistant-feature-flags.js", () => ({
76
- isAssistantFeatureFlagEnabled: (key: string) => {
77
- if (key === "permission-controls-v2") return flagEnabled;
78
- return true;
79
- },
80
- _setOverridesForTesting: () => {},
81
- clearFeatureFlagOverridesCache: () => {},
82
- getAssistantFeatureFlagDefaults: () => ({}),
83
- }));
84
-
85
- mock.module("../permissions/permission-mode-store.js", () => ({
86
- getMode: () => ({ askBeforeActing, hostAccess: false }),
87
- initPermissionModeStore: () => {},
88
- setAskBeforeActing: () => {},
89
- setHostAccess: () => {},
90
- onModeChanged: () => () => {},
91
- resetForTesting: () => {},
92
- }));
93
-
94
- // Import after mocks
95
67
  const { buildSystemPrompt } = await import("../prompts/system-prompt.js");
96
68
 
97
- const ACTION_CONFIRMATION_HEADING = "## Action Confirmation Mode";
69
+ const SOUL_PATH = join(TEST_DIR, "SOUL.md");
98
70
 
99
- describe("Action Confirmation Mode system prompt injection", () => {
71
+ afterAll(() => {
72
+ mock.restore();
73
+ });
74
+
75
+ describe("system prompt no longer injects ask-before-acting mode", () => {
100
76
  beforeEach(() => {
101
77
  mkdirSync(TEST_DIR, { recursive: true });
102
- flagEnabled = false;
103
- askBeforeActing = true;
78
+ writeFileSync(SOUL_PATH, "# Soul\n\nSOUL guidance survives.");
104
79
  });
105
80
 
106
81
  afterEach(() => {
107
- flagEnabled = false;
108
- askBeforeActing = true;
109
- });
110
-
111
- test("includes section when flag enabled and askBeforeActing is true", () => {
112
- flagEnabled = true;
113
- askBeforeActing = true;
114
- const result = buildSystemPrompt();
115
- expect(result).toContain(ACTION_CONFIRMATION_HEADING);
116
- expect(result).toContain('"Ask before acting" mode');
82
+ try {
83
+ rmSync(SOUL_PATH, { force: true });
84
+ } catch {
85
+ /* noop */
86
+ }
117
87
  });
118
88
 
119
- test("excludes section when flag is disabled", () => {
120
- flagEnabled = false;
121
- askBeforeActing = true;
122
- const result = buildSystemPrompt();
123
- expect(result).not.toContain(ACTION_CONFIRMATION_HEADING);
124
- });
125
-
126
- test("excludes section when askBeforeActing is false", () => {
127
- flagEnabled = true;
128
- askBeforeActing = false;
129
- const result = buildSystemPrompt();
130
- expect(result).not.toContain(ACTION_CONFIRMATION_HEADING);
131
- });
89
+ test("includes SOUL.md and does not inject action confirmation copy", () => {
90
+ const prompt = buildSystemPrompt();
132
91
 
133
- test("excludes section when both flag disabled and askBeforeActing false", () => {
134
- flagEnabled = false;
135
- askBeforeActing = false;
136
- const result = buildSystemPrompt();
137
- expect(result).not.toContain(ACTION_CONFIRMATION_HEADING);
92
+ expect(prompt).toContain("SOUL guidance survives.");
93
+ expect(prompt).not.toContain("Action Confirmation Mode");
138
94
  });
139
95
  });
@@ -31,6 +31,10 @@ mock.module("../util/logger.js", () => ({
31
31
  pruneOldLogFiles: () => 0,
32
32
  }));
33
33
 
34
+ // Mutable config used by the mocked loader so individual tests can override
35
+ // specific fields (e.g. systemPromptPrefix) without touching other sections.
36
+ const mockLoadedConfig: Record<string, unknown> = {};
37
+
34
38
  mock.module("../config/loader.js", () => ({
35
39
  getConfig: () => ({
36
40
  ui: {},
@@ -49,7 +53,7 @@ mock.module("../config/loader.js", () => ({
49
53
  "web-search": { mode: "your-own", provider: "inference-provider-native" },
50
54
  },
51
55
  }),
52
- loadConfig: () => ({}),
56
+ loadConfig: () => mockLoadedConfig,
53
57
  loadRawConfig: () => ({}),
54
58
  saveConfig: () => {},
55
59
  saveRawConfig: () => {},
@@ -123,6 +127,7 @@ describe("buildSystemPrompt", () => {
123
127
  const p = join(TEST_DIR, name);
124
128
  if (existsSync(p)) rmSync(p, { recursive: true, force: true });
125
129
  }
130
+ delete mockLoadedConfig.systemPromptPrefix;
126
131
  });
127
132
 
128
133
  test("returns empty string when no files exist", () => {
@@ -343,6 +348,72 @@ describe("buildSystemPrompt", () => {
343
348
  const result = buildSystemPrompt();
344
349
  expect(basePrompt(result)).toBe("");
345
350
  });
351
+
352
+ describe("custom systemPromptPrefix", () => {
353
+ test("omits prefix when config value is unset", () => {
354
+ const result = buildSystemPrompt();
355
+ // With no prefix, the prompt should start with the parallel tool calls
356
+ // section (the first static section when no prefix is injected).
357
+ expect(result.startsWith("<use_parallel_tool_calls>")).toBe(true);
358
+ });
359
+
360
+ test("omits prefix when config value is null", () => {
361
+ mockLoadedConfig.systemPromptPrefix = null;
362
+ const result = buildSystemPrompt();
363
+ expect(result.startsWith("<use_parallel_tool_calls>")).toBe(true);
364
+ });
365
+
366
+ test("omits prefix when config value is an empty string", () => {
367
+ mockLoadedConfig.systemPromptPrefix = "";
368
+ const result = buildSystemPrompt();
369
+ expect(result.startsWith("<use_parallel_tool_calls>")).toBe(true);
370
+ });
371
+
372
+ test("omits prefix when config value is whitespace-only", () => {
373
+ mockLoadedConfig.systemPromptPrefix = " \n\n ";
374
+ const result = buildSystemPrompt();
375
+ expect(result.startsWith("<use_parallel_tool_calls>")).toBe(true);
376
+ });
377
+
378
+ test("injects prefix at the very start of the prompt when set", () => {
379
+ mockLoadedConfig.systemPromptPrefix = "You are operating in demo mode.";
380
+ const result = buildSystemPrompt();
381
+ expect(result.startsWith("You are operating in demo mode.")).toBe(true);
382
+ // The standard static sections should still follow the prefix.
383
+ expect(result).toContain("<use_parallel_tool_calls>");
384
+ // Prefix lives in the static (cached) block, not the dynamic block.
385
+ const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
386
+ expect(boundaryIdx).toBeGreaterThan(-1);
387
+ const staticBlock = result.slice(0, boundaryIdx);
388
+ expect(staticBlock).toContain("You are operating in demo mode.");
389
+ });
390
+
391
+ test("trims leading/trailing whitespace from the prefix", () => {
392
+ mockLoadedConfig.systemPromptPrefix =
393
+ "\n\n Pretend you are a pirate. \n\n";
394
+ const result = buildSystemPrompt();
395
+ expect(result.startsWith("Pretend you are a pirate.")).toBe(true);
396
+ });
397
+
398
+ test("multi-line prefixes are preserved verbatim after trimming", () => {
399
+ mockLoadedConfig.systemPromptPrefix =
400
+ "# Org Guardrails\n\n- Never discuss pricing.\n- Escalate refunds.";
401
+ const result = buildSystemPrompt();
402
+ expect(
403
+ result.startsWith(
404
+ "# Org Guardrails\n\n- Never discuss pricing.\n- Escalate refunds.",
405
+ ),
406
+ ).toBe(true);
407
+ });
408
+
409
+ test("workspace file content still appears after prefix", () => {
410
+ mockLoadedConfig.systemPromptPrefix = "Custom prefix";
411
+ writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
412
+ const result = buildSystemPrompt();
413
+ expect(result.startsWith("Custom prefix")).toBe(true);
414
+ expect(basePrompt(result)).toBe("I am Vellum.");
415
+ });
416
+ });
346
417
  });
347
418
 
348
419
  describe("stripCommentLines", () => {