@vellumai/assistant 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +184 -69
- package/package.json +41 -41
- package/scripts/generate-openapi.ts +1 -2
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/browser-fill-credential.test.ts +229 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/catalog-files.test.ts +862 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +125 -48
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/context-overflow-approval.test.ts +16 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +1 -1
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-queue.test.ts +45 -2
- package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +2 -2
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/gemini-provider.test.ts +2 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +707 -371
- package/src/__tests__/headless-browser-navigate.test.ts +389 -47
- package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
- package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
- package/src/__tests__/host-bash-proxy.test.ts +150 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
- package/src/__tests__/host-cu-proxy.test.ts +171 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +61 -2
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +101 -1
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/oauth-apps-routes.test.ts +17 -12
- package/src/__tests__/oauth-cli.test.ts +707 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +50 -14
- package/src/__tests__/oauth-store.test.ts +1386 -182
- package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
- package/src/__tests__/onboarding-template-contract.test.ts +75 -57
- package/src/__tests__/openai-provider.test.ts +2 -2
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/require-fresh-approval.test.ts +40 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
- package/src/__tests__/slack-channel-config.test.ts +12 -15
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +72 -1
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/terminal-tools.test.ts +9 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +14 -29
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -6
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +53 -3
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/email.ts +18 -13
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +6 -3
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +200 -36
- package/src/cli/commands/oauth/shared.ts +5 -5
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/contacts/SKILL.md +3 -0
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/env-registry.ts +14 -0
- package/src/config/env.ts +21 -0
- package/src/config/feature-flag-registry.json +44 -5
- package/src/config/loader.ts +56 -1
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +46 -5
- package/src/config/schemas/host-browser.ts +66 -0
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +8 -0
- package/src/config/types.ts +0 -1
- package/src/context/post-turn-tool-result-truncation.ts +176 -0
- package/src/context/window-manager.ts +19 -1
- package/src/credential-execution/approval-bridge.ts +49 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/context-overflow-approval.ts +5 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +58 -24
- package/src/daemon/conversation-attachments.ts +40 -0
- package/src/daemon/conversation-process.ts +48 -1
- package/src/daemon/conversation-runtime-assembly.ts +118 -36
- package/src/daemon/conversation-surfaces.ts +37 -36
- package/src/daemon/conversation-tool-setup.ts +74 -8
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +226 -8
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -140
- package/src/daemon/handlers/shared.ts +58 -0
- package/src/daemon/handlers/skills.ts +232 -37
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +191 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +65 -11
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +55 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -5
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +92 -12
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +5 -24
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +23 -0
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +176 -17
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/retriever.ts +40 -22
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/llm-usage-store.ts +45 -4
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +1 -0
- package/src/memory/schema/oauth.ts +18 -13
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +8 -8
- package/src/oauth/byo-connection.ts +7 -7
- package/src/oauth/connect-orchestrator.ts +23 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +16 -16
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +214 -100
- package/src/oauth/platform-connection.test.ts +3 -3
- package/src/oauth/platform-connection.ts +4 -4
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +126 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -1
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -105
- package/src/providers/anthropic/client.ts +1 -0
- package/src/providers/types.ts +1 -1
- package/src/runtime/AGENTS.md +23 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/assistant-event-hub.ts +2 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +6 -7
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/chrome-extension-registry.ts +332 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +425 -70
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
- package/src/runtime/migrations/migration-transport.ts +6 -0
- package/src/runtime/migrations/migration-wizard.ts +22 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
- package/src/runtime/migrations/vbundle-importer.ts +55 -5
- package/src/runtime/pending-interactions.ts +29 -13
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/browser-cdp-routes.ts +229 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
- package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +301 -27
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/host-browser-routes.ts +279 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-routes.ts +259 -16
- package/src/runtime/routes/log-export-routes.ts +42 -22
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +87 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +9 -97
- package/src/runtime/routes/skills-routes.ts +52 -2
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/usage-routes.ts +8 -7
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/schedule/scheduler.ts +7 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +42 -25
- package/src/security/secure-keys.ts +118 -25
- package/src/security/token-manager.ts +23 -10
- package/src/skills/catalog-files.ts +492 -0
- package/src/subagent/manager.ts +131 -26
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +645 -340
- package/src/tools/browser/browser-manager.ts +36 -12
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
- package/src/tools/browser/cdp-client/errors.ts +34 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
- package/src/tools/browser/cdp-client/factory.ts +204 -0
- package/src/tools/browser/cdp-client/index.ts +14 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +52 -0
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/permission-checker.ts +77 -82
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -0
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/shell.ts +21 -16
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -0
- package/src/util/platform.ts +14 -19
- package/src/workspace/top-level-renderer.ts +19 -1
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/tools/browser/chrome-cdp.ts +0 -239
- 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
|
+
});
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
69
|
+
const SOUL_PATH = join(TEST_DIR, "SOUL.md");
|
|
98
70
|
|
|
99
|
-
|
|
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
|
-
|
|
103
|
-
askBeforeActing = true;
|
|
78
|
+
writeFileSync(SOUL_PATH, "# Soul\n\nSOUL guidance survives.");
|
|
104
79
|
});
|
|
105
80
|
|
|
106
81
|
afterEach(() => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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("
|
|
120
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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", () => {
|