@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
|
@@ -14,11 +14,6 @@
|
|
|
14
14
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
15
15
|
|
|
16
16
|
import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
|
|
17
|
-
import {
|
|
18
|
-
initPermissionModeStore,
|
|
19
|
-
resetForTesting as resetPermissionModeStore,
|
|
20
|
-
setHostAccess,
|
|
21
|
-
} from "../permissions/permission-mode-store.js";
|
|
22
17
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
23
18
|
import { RiskLevel } from "../permissions/types.js";
|
|
24
19
|
import { PermissionChecker } from "../tools/permission-checker.js";
|
|
@@ -47,6 +42,13 @@ mock.module("../hooks/manager.js", () => ({
|
|
|
47
42
|
}),
|
|
48
43
|
}));
|
|
49
44
|
|
|
45
|
+
const hostAccessByConversation = new Map<string, boolean>();
|
|
46
|
+
|
|
47
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
48
|
+
getConversationHostAccess: (conversationId: string) =>
|
|
49
|
+
hostAccessByConversation.get(conversationId) ?? false,
|
|
50
|
+
}));
|
|
51
|
+
|
|
50
52
|
// ---------------------------------------------------------------------------
|
|
51
53
|
// Helpers
|
|
52
54
|
// ---------------------------------------------------------------------------
|
|
@@ -96,13 +98,12 @@ const executionTarget: ExecutionTarget = "host";
|
|
|
96
98
|
|
|
97
99
|
beforeEach(() => {
|
|
98
100
|
_setOverridesForTesting({});
|
|
99
|
-
|
|
100
|
-
initPermissionModeStore();
|
|
101
|
+
hostAccessByConversation.clear();
|
|
101
102
|
});
|
|
102
103
|
|
|
103
104
|
afterEach(() => {
|
|
104
105
|
_setOverridesForTesting({});
|
|
105
|
-
|
|
106
|
+
hostAccessByConversation.clear();
|
|
106
107
|
});
|
|
107
108
|
|
|
108
109
|
// ---------------------------------------------------------------------------
|
|
@@ -125,7 +126,7 @@ describe("permission-checker host-access gate (v2)", () => {
|
|
|
125
126
|
|
|
126
127
|
describe("host tools with hostAccess=false", () => {
|
|
127
128
|
beforeEach(() => {
|
|
128
|
-
|
|
129
|
+
hostAccessByConversation.set("test-conv", false);
|
|
129
130
|
});
|
|
130
131
|
|
|
131
132
|
for (const toolName of HOST_TOOL_NAMES) {
|
|
@@ -151,6 +152,12 @@ describe("permission-checker host-access gate (v2)", () => {
|
|
|
151
152
|
|
|
152
153
|
// The prompter should have been called (interactive dialog)
|
|
153
154
|
expect(promptSpy).toHaveBeenCalled();
|
|
155
|
+
const call = promptSpy.mock.calls[0] as unknown as unknown[];
|
|
156
|
+
expect(call[3]).toEqual([]);
|
|
157
|
+
expect(call[4]).toEqual([]);
|
|
158
|
+
expect(call[8]).toBe(false);
|
|
159
|
+
expect(call[10]).toBeUndefined();
|
|
160
|
+
expect(call[12]).toBe(true);
|
|
154
161
|
// Since the mock prompter returns "allow", the result should be allowed
|
|
155
162
|
expect(result.allowed).toBe(true);
|
|
156
163
|
expect(result.decision).toBe("allow");
|
|
@@ -185,7 +192,7 @@ describe("permission-checker host-access gate (v2)", () => {
|
|
|
185
192
|
|
|
186
193
|
describe("host tools with hostAccess=true", () => {
|
|
187
194
|
beforeEach(() => {
|
|
188
|
-
|
|
195
|
+
hostAccessByConversation.set("test-conv", true);
|
|
189
196
|
});
|
|
190
197
|
|
|
191
198
|
for (const toolName of HOST_TOOL_NAMES) {
|
|
@@ -217,7 +224,7 @@ describe("permission-checker host-access gate (v2)", () => {
|
|
|
217
224
|
"web_search",
|
|
218
225
|
]) {
|
|
219
226
|
test(`${toolName} is auto-allowed regardless of hostAccess`, async () => {
|
|
220
|
-
|
|
227
|
+
hostAccessByConversation.set("test-conv", false);
|
|
221
228
|
const checker = new PermissionChecker(makePrompter());
|
|
222
229
|
const result = await checker.checkPermission(
|
|
223
230
|
toolName,
|
|
@@ -239,7 +246,7 @@ describe("permission-checker host-access gate (v2)", () => {
|
|
|
239
246
|
|
|
240
247
|
describe("requireFreshApproval bypasses v2 auto-allow", () => {
|
|
241
248
|
beforeEach(() => {
|
|
242
|
-
|
|
249
|
+
hostAccessByConversation.set("test-conv", true);
|
|
243
250
|
});
|
|
244
251
|
|
|
245
252
|
test("host tool with requireFreshApproval falls through to prompter", async () => {
|
|
@@ -263,6 +270,12 @@ describe("permission-checker host-access gate (v2)", () => {
|
|
|
263
270
|
);
|
|
264
271
|
|
|
265
272
|
expect(promptSpy).toHaveBeenCalled();
|
|
273
|
+
const call = promptSpy.mock.calls[0] as unknown as unknown[];
|
|
274
|
+
expect(call[3]).toEqual([]);
|
|
275
|
+
expect(call[4]).toEqual([]);
|
|
276
|
+
expect(call[8]).toBe(false);
|
|
277
|
+
expect(call[10]).toBeUndefined();
|
|
278
|
+
expect(call[12]).toBe(false);
|
|
266
279
|
expect(result.allowed).toBe(true);
|
|
267
280
|
expect(result.decision).toBe("allow");
|
|
268
281
|
});
|
|
@@ -288,6 +301,12 @@ describe("permission-checker host-access gate (v2)", () => {
|
|
|
288
301
|
);
|
|
289
302
|
|
|
290
303
|
expect(promptSpy).toHaveBeenCalled();
|
|
304
|
+
const call = promptSpy.mock.calls[0] as unknown as unknown[];
|
|
305
|
+
expect(call[3]).toEqual([]);
|
|
306
|
+
expect(call[4]).toEqual([]);
|
|
307
|
+
expect(call[8]).toBe(false);
|
|
308
|
+
expect(call[10]).toBeUndefined();
|
|
309
|
+
expect(call[12]).toBe(false);
|
|
291
310
|
expect(result.allowed).toBe(true);
|
|
292
311
|
expect(result.decision).toBe("allow");
|
|
293
312
|
});
|
|
@@ -295,7 +314,7 @@ describe("permission-checker host-access gate (v2)", () => {
|
|
|
295
314
|
|
|
296
315
|
describe("non-interactive guardian session with hostAccess=false", () => {
|
|
297
316
|
beforeEach(() => {
|
|
298
|
-
|
|
317
|
+
hostAccessByConversation.set("test-conv", false);
|
|
299
318
|
});
|
|
300
319
|
|
|
301
320
|
test("host tool is NOT auto-approved (denies instead of guardian_auto_approve)", async () => {
|
|
@@ -329,7 +348,7 @@ describe("permission-checker host-access gate (v2)", () => {
|
|
|
329
348
|
|
|
330
349
|
describe("forcePromptSideEffects bypasses v2 auto-allow for side-effect tools", () => {
|
|
331
350
|
beforeEach(() => {
|
|
332
|
-
|
|
351
|
+
hostAccessByConversation.set("test-conv", true);
|
|
333
352
|
});
|
|
334
353
|
|
|
335
354
|
test("host side-effect tool with forcePromptSideEffects falls through to prompter", async () => {
|
|
@@ -401,6 +420,47 @@ describe("permission-checker host-access gate (v2)", () => {
|
|
|
401
420
|
expect(result.decision).toBe("allow");
|
|
402
421
|
});
|
|
403
422
|
});
|
|
423
|
+
|
|
424
|
+
test("host access is evaluated per conversation", async () => {
|
|
425
|
+
hostAccessByConversation.set("allow-conv", true);
|
|
426
|
+
hostAccessByConversation.set("deny-conv", false);
|
|
427
|
+
|
|
428
|
+
const promptSpy = mock(() =>
|
|
429
|
+
Promise.resolve({ decision: "deny" as const }),
|
|
430
|
+
);
|
|
431
|
+
const checker = new PermissionChecker({
|
|
432
|
+
prompt: promptSpy,
|
|
433
|
+
} as unknown as PermissionPrompter);
|
|
434
|
+
|
|
435
|
+
const allowed = await checker.checkPermission(
|
|
436
|
+
"host_bash",
|
|
437
|
+
{},
|
|
438
|
+
makeTool("host_bash"),
|
|
439
|
+
makeContext({ conversationId: "allow-conv" }),
|
|
440
|
+
executionTarget,
|
|
441
|
+
noopEmit,
|
|
442
|
+
noopSanitize,
|
|
443
|
+
Date.now(),
|
|
444
|
+
noopDiff,
|
|
445
|
+
);
|
|
446
|
+
const denied = await checker.checkPermission(
|
|
447
|
+
"host_bash",
|
|
448
|
+
{},
|
|
449
|
+
makeTool("host_bash"),
|
|
450
|
+
makeContext({ conversationId: "deny-conv" }),
|
|
451
|
+
executionTarget,
|
|
452
|
+
noopEmit,
|
|
453
|
+
noopSanitize,
|
|
454
|
+
Date.now(),
|
|
455
|
+
noopDiff,
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
expect(allowed.allowed).toBe(true);
|
|
459
|
+
expect(allowed.decision).toBe("allow");
|
|
460
|
+
expect(denied.allowed).toBe(false);
|
|
461
|
+
expect(denied.decision).toBe("deny");
|
|
462
|
+
expect(promptSpy).toHaveBeenCalledTimes(1);
|
|
463
|
+
});
|
|
404
464
|
});
|
|
405
465
|
|
|
406
466
|
describe("when permission-controls-v2 flag is OFF", () => {
|
|
@@ -6,51 +6,28 @@ import {
|
|
|
6
6
|
PermissionModeSchema,
|
|
7
7
|
} from "../permissions/permission-mode.js";
|
|
8
8
|
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Tests: PermissionModeSchema
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
9
|
describe("PermissionModeSchema", () => {
|
|
14
|
-
test("parses empty object with
|
|
15
|
-
|
|
16
|
-
expect(result.askBeforeActing).toBe(true);
|
|
17
|
-
expect(result.hostAccess).toBe(false);
|
|
10
|
+
test("parses empty object with the host-access default", () => {
|
|
11
|
+
expect(PermissionModeSchema.parse({})).toEqual({ hostAccess: false });
|
|
18
12
|
});
|
|
19
13
|
|
|
20
14
|
test("DEFAULT_PERMISSION_MODE matches schema defaults", () => {
|
|
21
|
-
|
|
22
|
-
expect(parsed).toEqual(DEFAULT_PERMISSION_MODE);
|
|
15
|
+
expect(PermissionModeSchema.parse({})).toEqual(DEFAULT_PERMISSION_MODE);
|
|
23
16
|
});
|
|
24
17
|
|
|
25
|
-
test("accepts explicit
|
|
26
|
-
|
|
27
|
-
askBeforeActing: true,
|
|
18
|
+
test("accepts explicit host access values", () => {
|
|
19
|
+
expect(PermissionModeSchema.parse({ hostAccess: true })).toEqual({
|
|
28
20
|
hostAccess: true,
|
|
29
21
|
});
|
|
30
|
-
expect(
|
|
31
|
-
expect(result.hostAccess).toBe(true);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("accepts explicit false/false", () => {
|
|
35
|
-
const result = PermissionModeSchema.parse({
|
|
36
|
-
askBeforeActing: false,
|
|
22
|
+
expect(PermissionModeSchema.parse({ hostAccess: false })).toEqual({
|
|
37
23
|
hostAccess: false,
|
|
38
24
|
});
|
|
39
|
-
expect(result.askBeforeActing).toBe(false);
|
|
40
|
-
expect(result.hostAccess).toBe(false);
|
|
41
25
|
});
|
|
42
26
|
|
|
43
27
|
test("round-trips through JSON serialization", () => {
|
|
44
|
-
const original = {
|
|
28
|
+
const original = { hostAccess: true };
|
|
45
29
|
const json = JSON.stringify(original);
|
|
46
|
-
|
|
47
|
-
expect(parsed).toEqual(original);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("rejects non-boolean askBeforeActing", () => {
|
|
51
|
-
expect(() =>
|
|
52
|
-
PermissionModeSchema.parse({ askBeforeActing: "yes" }),
|
|
53
|
-
).toThrow();
|
|
30
|
+
expect(PermissionModeSchema.parse(JSON.parse(json))).toEqual(original);
|
|
54
31
|
});
|
|
55
32
|
|
|
56
33
|
test("rejects non-boolean hostAccess", () => {
|
|
@@ -58,44 +35,39 @@ describe("PermissionModeSchema", () => {
|
|
|
58
35
|
});
|
|
59
36
|
});
|
|
60
37
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const result = PermissionsConfigSchema.parse({});
|
|
68
|
-
expect(result.askBeforeActing).toBe(true);
|
|
69
|
-
expect(result.hostAccess).toBe(false);
|
|
38
|
+
describe("PermissionsConfigSchema", () => {
|
|
39
|
+
test("defaults to workspace mode with host access disabled", () => {
|
|
40
|
+
expect(PermissionsConfigSchema.parse({})).toEqual({
|
|
41
|
+
mode: "workspace",
|
|
42
|
+
hostAccess: false,
|
|
43
|
+
});
|
|
70
44
|
});
|
|
71
45
|
|
|
72
|
-
test("preserves
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
46
|
+
test("preserves the mode field alongside hostAccess", () => {
|
|
47
|
+
expect(PermissionsConfigSchema.parse({ mode: "strict" })).toEqual({
|
|
48
|
+
mode: "strict",
|
|
49
|
+
hostAccess: false,
|
|
50
|
+
});
|
|
77
51
|
});
|
|
78
52
|
|
|
79
|
-
test("accepts overridden values
|
|
80
|
-
|
|
53
|
+
test("accepts overridden hostAccess values", () => {
|
|
54
|
+
expect(
|
|
55
|
+
PermissionsConfigSchema.parse({
|
|
56
|
+
mode: "workspace",
|
|
57
|
+
hostAccess: true,
|
|
58
|
+
}),
|
|
59
|
+
).toEqual({
|
|
81
60
|
mode: "workspace",
|
|
82
|
-
askBeforeActing: false,
|
|
83
61
|
hostAccess: true,
|
|
84
62
|
});
|
|
85
|
-
expect(result.mode).toBe("workspace");
|
|
86
|
-
expect(result.askBeforeActing).toBe(false);
|
|
87
|
-
expect(result.hostAccess).toBe(true);
|
|
88
63
|
});
|
|
89
64
|
|
|
90
|
-
test("round-trips
|
|
65
|
+
test("round-trips hostAccess through JSON serialization", () => {
|
|
91
66
|
const input = {
|
|
92
67
|
mode: "workspace" as const,
|
|
93
|
-
askBeforeActing: false,
|
|
94
68
|
hostAccess: true,
|
|
95
69
|
};
|
|
96
70
|
const json = JSON.stringify(input);
|
|
97
|
-
|
|
98
|
-
expect(parsed.askBeforeActing).toBe(false);
|
|
99
|
-
expect(parsed.hostAccess).toBe(true);
|
|
71
|
+
expect(PermissionsConfigSchema.parse(JSON.parse(json))).toEqual(input);
|
|
100
72
|
});
|
|
101
73
|
});
|
|
@@ -69,6 +69,25 @@ describe("platform callback registration", () => {
|
|
|
69
69
|
expect(context.authHeader).toBe("Api-Key ast-managed-key");
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
test("self-hosted assistant with stored credentials is enabled without IS_PLATFORM", async () => {
|
|
73
|
+
mockIsPlatform = false;
|
|
74
|
+
mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
|
|
75
|
+
"https://platform.example.com";
|
|
76
|
+
mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
|
|
77
|
+
"22222222-3333-4444-8555-666666666666";
|
|
78
|
+
mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
|
|
79
|
+
"ast-self-hosted-key";
|
|
80
|
+
|
|
81
|
+
const context = await resolvePlatformCallbackRegistrationContext();
|
|
82
|
+
|
|
83
|
+
expect(context.enabled).toBe(true);
|
|
84
|
+
expect(context.isPlatform).toBe(false);
|
|
85
|
+
expect(context.platformBaseUrl).toBe("https://platform.example.com");
|
|
86
|
+
expect(context.assistantId).toBe("22222222-3333-4444-8555-666666666666");
|
|
87
|
+
expect(context.hasAssistantApiKey).toBe(true);
|
|
88
|
+
expect(context.authHeader).toBe("Api-Key ast-self-hosted-key");
|
|
89
|
+
});
|
|
90
|
+
|
|
72
91
|
test("registerCallbackRoute falls back to assistant API key auth", async () => {
|
|
73
92
|
mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
|
|
74
93
|
"https://platform.example.com";
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
buildTruncatedContent,
|
|
8
|
+
derefToolResultReReads,
|
|
9
|
+
getToolResultFilePath,
|
|
10
|
+
postTurnTruncateToolResults,
|
|
11
|
+
REREAD_STUB,
|
|
12
|
+
TARGET_CHARS,
|
|
13
|
+
THRESHOLD_CHARS,
|
|
14
|
+
TOOL_RESULT_DIR,
|
|
15
|
+
TRUNCATION_MARKER,
|
|
16
|
+
} from "../context/post-turn-tool-result-truncation.js";
|
|
17
|
+
import type { ContentBlock, Message } from "../providers/types.js";
|
|
18
|
+
|
|
19
|
+
function makeToolResult(
|
|
20
|
+
content: string,
|
|
21
|
+
toolUseId = "tool_use_1",
|
|
22
|
+
is_error = false,
|
|
23
|
+
): ContentBlock {
|
|
24
|
+
return {
|
|
25
|
+
type: "tool_result" as const,
|
|
26
|
+
tool_use_id: toolUseId,
|
|
27
|
+
content,
|
|
28
|
+
...(is_error ? { is_error: true } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function makeMessages(blocks: ContentBlock[]): Message[] {
|
|
33
|
+
return [{ role: "user", content: blocks }];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("postTurnTruncateToolResults", () => {
|
|
37
|
+
let convDir: string;
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
convDir = mkdtempSync(join(tmpdir(), "tool-result-trunc-"));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
rmSync(convDir, { recursive: true, force: true });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("result below threshold is returned unchanged, no file written", () => {
|
|
48
|
+
const shortContent = "a".repeat(THRESHOLD_CHARS);
|
|
49
|
+
const messages = makeMessages([makeToolResult(shortContent)]);
|
|
50
|
+
|
|
51
|
+
const { messages: result, truncatedCount } =
|
|
52
|
+
postTurnTruncateToolResults(messages, { conversationDir: convDir });
|
|
53
|
+
|
|
54
|
+
expect(truncatedCount).toBe(0);
|
|
55
|
+
expect(result).toBe(messages); // same reference — no copy
|
|
56
|
+
expect(existsSync(join(convDir, TOOL_RESULT_DIR))).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("result above threshold is truncated, file written with original content", () => {
|
|
60
|
+
const longContent = "x".repeat(THRESHOLD_CHARS + 1);
|
|
61
|
+
const toolUseId = "tool_use_abc";
|
|
62
|
+
const messages = makeMessages([makeToolResult(longContent, toolUseId)]);
|
|
63
|
+
|
|
64
|
+
const { messages: result, truncatedCount } =
|
|
65
|
+
postTurnTruncateToolResults(messages, { conversationDir: convDir });
|
|
66
|
+
|
|
67
|
+
expect(truncatedCount).toBe(1);
|
|
68
|
+
|
|
69
|
+
const block = result[0].content[0] as { type: "tool_result"; content: string };
|
|
70
|
+
expect(block.content).toContain(TRUNCATION_MARKER);
|
|
71
|
+
expect(block.content.length).toBeLessThan(longContent.length);
|
|
72
|
+
|
|
73
|
+
// Verify file on disk contains original content.
|
|
74
|
+
const filePath = getToolResultFilePath(convDir, toolUseId);
|
|
75
|
+
expect(existsSync(filePath)).toBe(true);
|
|
76
|
+
expect(readFileSync(filePath, "utf-8")).toBe(longContent);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("error result above threshold is unchanged", () => {
|
|
80
|
+
const longContent = "e".repeat(THRESHOLD_CHARS + 100);
|
|
81
|
+
const messages = makeMessages([
|
|
82
|
+
makeToolResult(longContent, "tool_err", true),
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
const { messages: result, truncatedCount } =
|
|
86
|
+
postTurnTruncateToolResults(messages, { conversationDir: convDir });
|
|
87
|
+
|
|
88
|
+
expect(truncatedCount).toBe(0);
|
|
89
|
+
expect(result).toBe(messages);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("already-truncated result is unchanged (idempotency)", () => {
|
|
93
|
+
// Simulate a result that was already truncated in a prior pass.
|
|
94
|
+
const alreadyTruncated =
|
|
95
|
+
"prefix..." +
|
|
96
|
+
`\n\n...(500 tokens omitted ${TRUNCATION_MARKER} /some/path.txt)\n\n` +
|
|
97
|
+
"...suffix".padEnd(THRESHOLD_CHARS + 1, "z");
|
|
98
|
+
const messages = makeMessages([
|
|
99
|
+
makeToolResult(alreadyTruncated, "tool_idempotent"),
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
const { messages: result, truncatedCount } =
|
|
103
|
+
postTurnTruncateToolResults(messages, { conversationDir: convDir });
|
|
104
|
+
|
|
105
|
+
expect(truncatedCount).toBe(0);
|
|
106
|
+
expect(result).toBe(messages);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("multiple results in one turn are each evaluated independently", () => {
|
|
110
|
+
const short = "s".repeat(100);
|
|
111
|
+
const long1 = "a".repeat(THRESHOLD_CHARS + 1);
|
|
112
|
+
const long2 = "b".repeat(THRESHOLD_CHARS + 2);
|
|
113
|
+
const messages = makeMessages([
|
|
114
|
+
makeToolResult(short, "tool_short"),
|
|
115
|
+
makeToolResult(long1, "tool_long1"),
|
|
116
|
+
makeToolResult(long2, "tool_long2"),
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
const { messages: result, truncatedCount } =
|
|
120
|
+
postTurnTruncateToolResults(messages, { conversationDir: convDir });
|
|
121
|
+
|
|
122
|
+
expect(truncatedCount).toBe(2);
|
|
123
|
+
|
|
124
|
+
// Short result unchanged.
|
|
125
|
+
const b0 = result[0].content[0] as { type: "tool_result"; content: string };
|
|
126
|
+
expect(b0.content).toBe(short);
|
|
127
|
+
|
|
128
|
+
// Both long results truncated.
|
|
129
|
+
const b1 = result[0].content[1] as { type: "tool_result"; content: string };
|
|
130
|
+
const b2 = result[0].content[2] as { type: "tool_result"; content: string };
|
|
131
|
+
expect(b1.content).toContain(TRUNCATION_MARKER);
|
|
132
|
+
expect(b2.content).toContain(TRUNCATION_MARKER);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("prefix/suffix split preserves first and last halves of TARGET_CHARS", () => {
|
|
136
|
+
// Build content where each char is its position modulo 10 so we can verify slicing.
|
|
137
|
+
const longContent = Array.from({ length: THRESHOLD_CHARS + 500 }, (_, i) =>
|
|
138
|
+
String(i % 10),
|
|
139
|
+
).join("");
|
|
140
|
+
|
|
141
|
+
const filePath = "/tmp/fake-path.txt";
|
|
142
|
+
const stub = buildTruncatedContent(longContent, filePath);
|
|
143
|
+
|
|
144
|
+
const half = Math.floor(TARGET_CHARS / 2);
|
|
145
|
+
const expectedPrefix = longContent.slice(0, half);
|
|
146
|
+
const expectedSuffix = longContent.slice(-half);
|
|
147
|
+
|
|
148
|
+
expect(stub.startsWith(expectedPrefix)).toBe(true);
|
|
149
|
+
expect(stub.endsWith(expectedSuffix)).toBe(true);
|
|
150
|
+
expect(stub).toContain(TRUNCATION_MARKER);
|
|
151
|
+
expect(stub).toContain(filePath);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("file path is deterministic for the same toolUseId", () => {
|
|
155
|
+
const id = "tool_use_deterministic";
|
|
156
|
+
const path1 = getToolResultFilePath("/some/dir", id);
|
|
157
|
+
const path2 = getToolResultFilePath("/some/dir", id);
|
|
158
|
+
expect(path1).toBe(path2);
|
|
159
|
+
|
|
160
|
+
// Different IDs produce different paths.
|
|
161
|
+
const path3 = getToolResultFilePath("/some/dir", "tool_use_other");
|
|
162
|
+
expect(path3).not.toBe(path1);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("derefToolResultReReads", () => {
|
|
167
|
+
function makeToolUse(
|
|
168
|
+
id: string,
|
|
169
|
+
name: string,
|
|
170
|
+
input: Record<string, unknown>,
|
|
171
|
+
): ContentBlock {
|
|
172
|
+
return { type: "tool_use" as const, id, name, input };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
test("file_read of .tool-results/ path: tool_result content replaced with REREAD_STUB", () => {
|
|
176
|
+
const toolUseId = "tu_reread_1";
|
|
177
|
+
const messages: Message[] = [
|
|
178
|
+
{
|
|
179
|
+
role: "assistant",
|
|
180
|
+
content: [
|
|
181
|
+
makeToolUse(toolUseId, "file_read", {
|
|
182
|
+
path: `/home/user/.vellum/workspace/conversations/abc/${TOOL_RESULT_DIR}/abc123.txt`,
|
|
183
|
+
}),
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
role: "user",
|
|
188
|
+
content: [makeToolResult("full file contents here", toolUseId)],
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const { messages: result, dereferencedCount } =
|
|
193
|
+
derefToolResultReReads(messages);
|
|
194
|
+
|
|
195
|
+
expect(dereferencedCount).toBe(1);
|
|
196
|
+
const block = result[1].content[0] as { type: "tool_result"; content: string };
|
|
197
|
+
expect(block.content).toBe(REREAD_STUB);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("file_read of normal path: tool_result unchanged", () => {
|
|
201
|
+
const toolUseId = "tu_normal_read";
|
|
202
|
+
const originalContent = "some file contents";
|
|
203
|
+
const messages: Message[] = [
|
|
204
|
+
{
|
|
205
|
+
role: "assistant",
|
|
206
|
+
content: [
|
|
207
|
+
makeToolUse(toolUseId, "file_read", {
|
|
208
|
+
path: "src/foo.ts",
|
|
209
|
+
}),
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
role: "user",
|
|
214
|
+
content: [makeToolResult(originalContent, toolUseId)],
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
const { messages: result, dereferencedCount } =
|
|
219
|
+
derefToolResultReReads(messages);
|
|
220
|
+
|
|
221
|
+
expect(dereferencedCount).toBe(0);
|
|
222
|
+
expect(result).toBe(messages); // same reference — no copy
|
|
223
|
+
const block = result[1].content[0] as { type: "tool_result"; content: string };
|
|
224
|
+
expect(block.content).toBe(originalContent);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("non-file_read tool: tool_result unchanged even if output mentions .tool-results/", () => {
|
|
228
|
+
const toolUseId = "tu_bash";
|
|
229
|
+
const outputMentioningDir = `Found file at /home/user/${TOOL_RESULT_DIR}/abc.txt`;
|
|
230
|
+
const messages: Message[] = [
|
|
231
|
+
{
|
|
232
|
+
role: "assistant",
|
|
233
|
+
content: [
|
|
234
|
+
makeToolUse(toolUseId, "bash", {
|
|
235
|
+
command: "ls",
|
|
236
|
+
}),
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
role: "user",
|
|
241
|
+
content: [makeToolResult(outputMentioningDir, toolUseId)],
|
|
242
|
+
},
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
const { messages: result, dereferencedCount } =
|
|
246
|
+
derefToolResultReReads(messages);
|
|
247
|
+
|
|
248
|
+
expect(dereferencedCount).toBe(0);
|
|
249
|
+
expect(result).toBe(messages);
|
|
250
|
+
const block = result[1].content[0] as { type: "tool_result"; content: string };
|
|
251
|
+
expect(block.content).toBe(outputMentioningDir);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("multiple re-reads in one turn: each deduplicated independently", () => {
|
|
255
|
+
const tu1 = "tu_multi_1";
|
|
256
|
+
const tu2 = "tu_multi_2";
|
|
257
|
+
const tuNormal = "tu_multi_normal";
|
|
258
|
+
const messages: Message[] = [
|
|
259
|
+
{
|
|
260
|
+
role: "assistant",
|
|
261
|
+
content: [
|
|
262
|
+
makeToolUse(tu1, "file_read", {
|
|
263
|
+
path: `/workspace/conv/${TOOL_RESULT_DIR}/aaa.txt`,
|
|
264
|
+
}),
|
|
265
|
+
makeToolUse(tu2, "file_read", {
|
|
266
|
+
path: `/workspace/conv/${TOOL_RESULT_DIR}/bbb.txt`,
|
|
267
|
+
}),
|
|
268
|
+
makeToolUse(tuNormal, "file_read", {
|
|
269
|
+
path: "src/bar.ts",
|
|
270
|
+
}),
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
role: "user",
|
|
275
|
+
content: [
|
|
276
|
+
makeToolResult("re-read content 1", tu1),
|
|
277
|
+
makeToolResult("re-read content 2", tu2),
|
|
278
|
+
makeToolResult("normal read content", tuNormal),
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
const { messages: result, dereferencedCount } =
|
|
284
|
+
derefToolResultReReads(messages);
|
|
285
|
+
|
|
286
|
+
expect(dereferencedCount).toBe(2);
|
|
287
|
+
|
|
288
|
+
const b0 = result[1].content[0] as { type: "tool_result"; content: string };
|
|
289
|
+
const b1 = result[1].content[1] as { type: "tool_result"; content: string };
|
|
290
|
+
const b2 = result[1].content[2] as { type: "tool_result"; content: string };
|
|
291
|
+
|
|
292
|
+
expect(b0.content).toBe(REREAD_STUB);
|
|
293
|
+
expect(b1.content).toBe(REREAD_STUB);
|
|
294
|
+
expect(b2.content).toBe("normal read content"); // unchanged
|
|
295
|
+
});
|
|
296
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
|
|
3
4
|
import type { ProxyApprovalRequest } from "../outbound-proxy/index.js";
|
|
4
5
|
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
@@ -110,6 +111,23 @@ describe("createProxyApprovalCallback", () => {
|
|
|
110
111
|
addRuleMock.mockClear();
|
|
111
112
|
findHighestPriorityRuleMock.mockClear();
|
|
112
113
|
findHighestPriorityRuleMock.mockReturnValue(null);
|
|
114
|
+
_setOverridesForTesting({});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("suppresses network approval cards under v2 and auto-allows", async () => {
|
|
118
|
+
_setOverridesForTesting({ "permission-controls-v2": true });
|
|
119
|
+
|
|
120
|
+
const ctx = makeContext();
|
|
121
|
+
const prompterSendToClient = mock(() => {});
|
|
122
|
+
const prompter = new PermissionPrompter(prompterSendToClient);
|
|
123
|
+
|
|
124
|
+
const callback = createProxyApprovalCallback(prompter, ctx);
|
|
125
|
+
const result = await callback(makeAskMissingCredentialRequest());
|
|
126
|
+
|
|
127
|
+
expect(result).toBe(true);
|
|
128
|
+
expect(prompterSendToClient).not.toHaveBeenCalled();
|
|
129
|
+
expect(findHighestPriorityRuleMock).not.toHaveBeenCalled();
|
|
130
|
+
expect(addRuleMock).not.toHaveBeenCalled();
|
|
113
131
|
});
|
|
114
132
|
|
|
115
133
|
test("returns true when user allows an ask_missing_credential request", async () => {
|