@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
|
@@ -79,12 +79,6 @@ export const PermissionsConfigSchema = z
|
|
|
79
79
|
.describe(
|
|
80
80
|
"Permission mode — 'strict' requires explicit approval for all operations, 'workspace' allows operations within the workspace",
|
|
81
81
|
),
|
|
82
|
-
askBeforeActing: z
|
|
83
|
-
.boolean({
|
|
84
|
-
error: "permissions.askBeforeActing must be a boolean",
|
|
85
|
-
})
|
|
86
|
-
.default(true)
|
|
87
|
-
.describe("Whether the assistant should check in before taking actions"),
|
|
88
82
|
hostAccess: z
|
|
89
83
|
.boolean({
|
|
90
84
|
error: "permissions.hostAccess must be a boolean",
|
|
@@ -61,6 +61,11 @@ export const LinearOAuthServiceSchema = BaseServiceSchema.extend({
|
|
|
61
61
|
});
|
|
62
62
|
export type LinearOAuthService = z.infer<typeof LinearOAuthServiceSchema>;
|
|
63
63
|
|
|
64
|
+
export const GitHubOAuthServiceSchema = BaseServiceSchema.extend({
|
|
65
|
+
mode: ServiceModeSchema.default("your-own"),
|
|
66
|
+
});
|
|
67
|
+
export type GitHubOAuthService = z.infer<typeof GitHubOAuthServiceSchema>;
|
|
68
|
+
|
|
64
69
|
export const ServicesSchema = z.object({
|
|
65
70
|
inference: InferenceServiceSchema.default(InferenceServiceSchema.parse({})),
|
|
66
71
|
"image-generation": ImageGenerationServiceSchema.default(
|
|
@@ -78,5 +83,8 @@ export const ServicesSchema = z.object({
|
|
|
78
83
|
"linear-oauth": LinearOAuthServiceSchema.default(
|
|
79
84
|
LinearOAuthServiceSchema.parse({}),
|
|
80
85
|
),
|
|
86
|
+
"github-oauth": GitHubOAuthServiceSchema.default(
|
|
87
|
+
GitHubOAuthServiceSchema.parse({}),
|
|
88
|
+
),
|
|
81
89
|
});
|
|
82
90
|
export type Services = z.infer<typeof ServicesSchema>;
|
package/src/config/types.ts
CHANGED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
ContentBlock,
|
|
7
|
+
Message,
|
|
8
|
+
ToolResultContent,
|
|
9
|
+
ToolUseContent,
|
|
10
|
+
} from "../providers/types.js";
|
|
11
|
+
|
|
12
|
+
/** Minimum content length (chars) before a tool result is eligible for truncation. ~2000 tokens at 4 chars/token. */
|
|
13
|
+
export const THRESHOLD_CHARS = 8_000;
|
|
14
|
+
|
|
15
|
+
/** Target size (chars) for the truncated stub. ~300 tokens at 4 chars/token. */
|
|
16
|
+
export const TARGET_CHARS = 1_200;
|
|
17
|
+
|
|
18
|
+
/** Subdirectory name under the conversation directory for saved full results. */
|
|
19
|
+
export const TOOL_RESULT_DIR = ".tool-results";
|
|
20
|
+
|
|
21
|
+
/** Marker used to detect already-truncated results (idempotency guard). */
|
|
22
|
+
export const TRUNCATION_MARKER = "\u2014 full result:";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Deterministic file path for a tool result's full content on disk.
|
|
26
|
+
* Uses the first 12 hex chars of the SHA-256 of the tool_use_id.
|
|
27
|
+
*/
|
|
28
|
+
export function getToolResultFilePath(
|
|
29
|
+
conversationDir: string,
|
|
30
|
+
toolUseId: string,
|
|
31
|
+
): string {
|
|
32
|
+
const hash = createHash("sha256").update(toolUseId).digest("hex").slice(0, 12);
|
|
33
|
+
return join(conversationDir, TOOL_RESULT_DIR, `${hash}.txt`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build the truncated stub that replaces a large tool result in context.
|
|
38
|
+
* Preserves the first and last halves of TARGET_CHARS, with a middle marker
|
|
39
|
+
* indicating how many tokens were omitted and where to find the full result.
|
|
40
|
+
*/
|
|
41
|
+
export function buildTruncatedContent(
|
|
42
|
+
original: string,
|
|
43
|
+
filePath: string,
|
|
44
|
+
): string {
|
|
45
|
+
const half = Math.floor(TARGET_CHARS / 2);
|
|
46
|
+
const prefix = original.slice(0, half);
|
|
47
|
+
const suffix = original.slice(-half);
|
|
48
|
+
const omittedChars = original.length - TARGET_CHARS;
|
|
49
|
+
const estimatedTokens = Math.round(omittedChars / 4);
|
|
50
|
+
return `${prefix}\n\n...(${estimatedTokens} tokens omitted ${TRUNCATION_MARKER} ${filePath})\n\n${suffix}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Walk all messages and truncate tool results that exceed `THRESHOLD_CHARS`.
|
|
55
|
+
*
|
|
56
|
+
* For each eligible result:
|
|
57
|
+
* - The full content is persisted to a deterministic file path on disk.
|
|
58
|
+
* - The in-context content is replaced with a prefix/suffix stub.
|
|
59
|
+
*
|
|
60
|
+
* Results are skipped if they are below threshold, are error results,
|
|
61
|
+
* or have already been truncated (contain `TRUNCATION_MARKER`).
|
|
62
|
+
*
|
|
63
|
+
* Returns a shallow-copied messages array (only modified messages are cloned)
|
|
64
|
+
* and the count of results that were truncated.
|
|
65
|
+
*/
|
|
66
|
+
export function postTurnTruncateToolResults(
|
|
67
|
+
messages: Message[],
|
|
68
|
+
options: { conversationDir: string },
|
|
69
|
+
): { messages: Message[]; truncatedCount: number } {
|
|
70
|
+
let truncatedCount = 0;
|
|
71
|
+
|
|
72
|
+
const mapped = messages.map((msg) => {
|
|
73
|
+
let changed = false;
|
|
74
|
+
const nextContent: ContentBlock[] = msg.content.map((block) => {
|
|
75
|
+
if (block.type !== "tool_result") return block;
|
|
76
|
+
const tr = block as ToolResultContent;
|
|
77
|
+
|
|
78
|
+
// Skip short results.
|
|
79
|
+
if (tr.content.length <= THRESHOLD_CHARS) return block;
|
|
80
|
+
|
|
81
|
+
// Skip error results.
|
|
82
|
+
if (tr.is_error) return block;
|
|
83
|
+
|
|
84
|
+
// Skip already-truncated results (idempotency).
|
|
85
|
+
if (tr.content.includes(TRUNCATION_MARKER)) return block;
|
|
86
|
+
|
|
87
|
+
const filePath = getToolResultFilePath(
|
|
88
|
+
options.conversationDir,
|
|
89
|
+
tr.tool_use_id,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Persist full content to disk.
|
|
93
|
+
mkdirSync(join(options.conversationDir, TOOL_RESULT_DIR), {
|
|
94
|
+
recursive: true,
|
|
95
|
+
});
|
|
96
|
+
writeFileSync(filePath, tr.content, "utf-8");
|
|
97
|
+
|
|
98
|
+
changed = true;
|
|
99
|
+
truncatedCount++;
|
|
100
|
+
return {
|
|
101
|
+
...tr,
|
|
102
|
+
content: buildTruncatedContent(tr.content, filePath),
|
|
103
|
+
} as ContentBlock;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return changed ? { ...msg, content: nextContent } : msg;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return { messages: truncatedCount > 0 ? mapped : messages, truncatedCount };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Stub that replaces a re-read of a saved tool result to avoid context duplication. */
|
|
113
|
+
export const REREAD_STUB =
|
|
114
|
+
"(Re-read of saved tool result — original context is preserved above)";
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Deduplicate re-reads of saved tool results.
|
|
118
|
+
*
|
|
119
|
+
* When `postTurnTruncateToolResults` truncates a large result, it saves the full
|
|
120
|
+
* content to a `.tool-results/` file. If the model later calls `file_read` on that
|
|
121
|
+
* saved file, the result is a second copy of content whose truncated prefix/suffix
|
|
122
|
+
* is already in context. This function detects those re-reads and replaces their
|
|
123
|
+
* tool_result content with a short stub to avoid duplication.
|
|
124
|
+
*/
|
|
125
|
+
export function derefToolResultReReads(messages: Message[]): {
|
|
126
|
+
messages: Message[];
|
|
127
|
+
dereferencedCount: number;
|
|
128
|
+
} {
|
|
129
|
+
// Build a set of tool_use_ids that are file_read calls targeting .tool-results/ paths.
|
|
130
|
+
const reReadToolUseIds = new Set<string>();
|
|
131
|
+
|
|
132
|
+
for (const msg of messages) {
|
|
133
|
+
if (msg.role !== "assistant") continue;
|
|
134
|
+
for (const block of msg.content) {
|
|
135
|
+
if (block.type !== "tool_use") continue;
|
|
136
|
+
const tu = block as ToolUseContent;
|
|
137
|
+
if (tu.name !== "file_read") continue;
|
|
138
|
+
const filePath = tu.input.path ?? tu.input.file_path;
|
|
139
|
+
if (typeof filePath !== "string") continue;
|
|
140
|
+
if (filePath.includes(`/${TOOL_RESULT_DIR}/`)) {
|
|
141
|
+
reReadToolUseIds.add(tu.id);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (reReadToolUseIds.size === 0) {
|
|
147
|
+
return { messages, dereferencedCount: 0 };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let dereferencedCount = 0;
|
|
151
|
+
|
|
152
|
+
const mapped = messages.map((msg) => {
|
|
153
|
+
if (msg.role !== "user") return msg;
|
|
154
|
+
|
|
155
|
+
let changed = false;
|
|
156
|
+
const nextContent: ContentBlock[] = msg.content.map((block) => {
|
|
157
|
+
if (block.type !== "tool_result") return block;
|
|
158
|
+
const tr = block as ToolResultContent;
|
|
159
|
+
if (!reReadToolUseIds.has(tr.tool_use_id)) return block;
|
|
160
|
+
|
|
161
|
+
// Skip error results — preserve diagnostics (e.g. file not found).
|
|
162
|
+
if (tr.is_error) return block;
|
|
163
|
+
|
|
164
|
+
changed = true;
|
|
165
|
+
dereferencedCount++;
|
|
166
|
+
return { ...tr, content: REREAD_STUB } as ContentBlock;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return changed ? { ...msg, content: nextContent } : msg;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
messages: dereferencedCount > 0 ? mapped : messages,
|
|
174
|
+
dereferencedCount,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -102,6 +102,14 @@ export class ContextWindowManager {
|
|
|
102
102
|
private readonly _systemPrompt: string | (() => string);
|
|
103
103
|
private readonly config: ContextWindowConfig;
|
|
104
104
|
private readonly toolTokenBudget: number;
|
|
105
|
+
/**
|
|
106
|
+
* Number of leading messages that are non-persisted (injected inherited
|
|
107
|
+
* context from a parent conversation). `countPersistedMessages` subtracts
|
|
108
|
+
* this so `compactedPersistedMessages` only reflects DB-backed messages.
|
|
109
|
+
* Set by `Conversation.injectInheritedContext` and consumed (decremented)
|
|
110
|
+
* after a successful compaction pass.
|
|
111
|
+
*/
|
|
112
|
+
nonPersistedPrefixCount = 0;
|
|
105
113
|
/**
|
|
106
114
|
* Cached resolved system prompt. Lazily populated on first access via the
|
|
107
115
|
* `systemPrompt` getter and cleared after each compaction pass so the next
|
|
@@ -305,8 +313,12 @@ export class ContextWindowManager {
|
|
|
305
313
|
};
|
|
306
314
|
}
|
|
307
315
|
|
|
316
|
+
const injectedInCompactable = Math.min(
|
|
317
|
+
Math.max(0, this.nonPersistedPrefixCount - summaryOffset),
|
|
318
|
+
compactableMessages.length,
|
|
319
|
+
);
|
|
308
320
|
const compactedPersistedMessages =
|
|
309
|
-
countPersistedMessages(compactableMessages);
|
|
321
|
+
countPersistedMessages(compactableMessages) - injectedInCompactable;
|
|
310
322
|
const rawProjectedMessages = [
|
|
311
323
|
createContextSummaryMessage(existingSummary ?? "Projected summary"),
|
|
312
324
|
...messages.slice(keepPlan.keepFromIndex),
|
|
@@ -452,6 +464,12 @@ export class ContextWindowManager {
|
|
|
452
464
|
toolTokenBudget: this.toolTokenBudget,
|
|
453
465
|
},
|
|
454
466
|
);
|
|
467
|
+
// Consume the injected prefix messages that were compacted away.
|
|
468
|
+
this.nonPersistedPrefixCount = Math.max(
|
|
469
|
+
0,
|
|
470
|
+
this.nonPersistedPrefixCount - injectedInCompactable,
|
|
471
|
+
);
|
|
472
|
+
|
|
455
473
|
log.info(
|
|
456
474
|
{
|
|
457
475
|
previousEstimatedInputTokens,
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
|
|
22
22
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
23
23
|
import type { UserDecision } from "../permissions/types.js";
|
|
24
|
+
import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
|
|
24
25
|
import { getLogger } from "../util/logger.js";
|
|
25
26
|
import type { CesClient } from "./client.js";
|
|
26
27
|
|
|
@@ -173,13 +174,7 @@ export async function bridgeCesApproval(
|
|
|
173
174
|
signal?: AbortSignal;
|
|
174
175
|
},
|
|
175
176
|
): Promise<CesApprovalBridgeResult> {
|
|
176
|
-
const {
|
|
177
|
-
proposal,
|
|
178
|
-
renderedProposal,
|
|
179
|
-
proposalHash,
|
|
180
|
-
sessionId,
|
|
181
|
-
conversationId,
|
|
182
|
-
} = approval;
|
|
177
|
+
const { proposal, renderedProposal, proposalHash, sessionId } = approval;
|
|
183
178
|
|
|
184
179
|
// Non-interactive sessions have no client to respond — fail closed.
|
|
185
180
|
if (options?.isInteractive === false) {
|
|
@@ -194,6 +189,24 @@ export async function bridgeCesApproval(
|
|
|
194
189
|
return { outcome: "denied", userDecision: "deny" };
|
|
195
190
|
}
|
|
196
191
|
|
|
192
|
+
if (isPermissionControlsV2Enabled()) {
|
|
193
|
+
log.info(
|
|
194
|
+
{
|
|
195
|
+
event: "ces_approval_bridge_v2_suppressed",
|
|
196
|
+
proposalHash,
|
|
197
|
+
sessionId,
|
|
198
|
+
},
|
|
199
|
+
"CES approval request auto-approved without deterministic prompt under v2",
|
|
200
|
+
);
|
|
201
|
+
const v2Decision = mapUserDecisionToCesDecision("allow");
|
|
202
|
+
return recordCesGrant({
|
|
203
|
+
approval,
|
|
204
|
+
cesClient,
|
|
205
|
+
decision: v2Decision,
|
|
206
|
+
reason: "permission_controls_v2_auto_allow",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
197
210
|
// Build the tool name and input for the confirmation prompt. The tool
|
|
198
211
|
// name uses a `ces:` prefix so the client can distinguish CES approval
|
|
199
212
|
// requests from regular tool confirmation prompts.
|
|
@@ -262,8 +275,29 @@ export async function bridgeCesApproval(
|
|
|
262
275
|
`CES approval bridge: guardian decision is "${cesDecision.grantDecision}"`,
|
|
263
276
|
);
|
|
264
277
|
|
|
265
|
-
|
|
266
|
-
|
|
278
|
+
return recordCesGrant({
|
|
279
|
+
approval,
|
|
280
|
+
cesClient,
|
|
281
|
+
decision: cesDecision,
|
|
282
|
+
reason: response.decisionContext,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function recordCesGrant({
|
|
287
|
+
approval,
|
|
288
|
+
cesClient,
|
|
289
|
+
decision,
|
|
290
|
+
reason,
|
|
291
|
+
}: {
|
|
292
|
+
approval: ApprovalRequired;
|
|
293
|
+
cesClient: CesClient;
|
|
294
|
+
decision: CesApprovalDecision;
|
|
295
|
+
reason?: string;
|
|
296
|
+
}): Promise<CesApprovalBridgeResult> {
|
|
297
|
+
const { proposal, proposalHash, sessionId, conversationId } = approval;
|
|
298
|
+
|
|
299
|
+
if (decision.grantDecision === "denied") {
|
|
300
|
+
return { outcome: "denied", userDecision: decision.userDecision };
|
|
267
301
|
}
|
|
268
302
|
|
|
269
303
|
// Commit the approved grant to CES via record_grant RPC.
|
|
@@ -274,12 +308,12 @@ export async function bridgeCesApproval(
|
|
|
274
308
|
decision: {
|
|
275
309
|
proposal,
|
|
276
310
|
proposalHash,
|
|
277
|
-
decision:
|
|
311
|
+
decision: decision.grantDecision,
|
|
278
312
|
decidedBy: "guardian",
|
|
279
313
|
decidedAt: new Date().toISOString(),
|
|
280
|
-
reason
|
|
281
|
-
ttl:
|
|
282
|
-
grantType:
|
|
314
|
+
reason,
|
|
315
|
+
ttl: decision.ttl,
|
|
316
|
+
grantType: decision.grantType,
|
|
283
317
|
},
|
|
284
318
|
sessionId,
|
|
285
319
|
conversationId,
|
|
@@ -323,7 +357,7 @@ export async function bridgeCesApproval(
|
|
|
323
357
|
proposalHash,
|
|
324
358
|
sessionId,
|
|
325
359
|
grantId,
|
|
326
|
-
userDecision:
|
|
360
|
+
userDecision: decision.userDecision,
|
|
327
361
|
},
|
|
328
362
|
"CES approval bridge: grant recorded successfully",
|
|
329
363
|
);
|
|
@@ -331,7 +365,7 @@ export async function bridgeCesApproval(
|
|
|
331
365
|
return {
|
|
332
366
|
outcome: "approved",
|
|
333
367
|
grantId,
|
|
334
|
-
userDecision:
|
|
368
|
+
userDecision: decision.userDecision,
|
|
335
369
|
};
|
|
336
370
|
} catch (err) {
|
|
337
371
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `isToolActiveForContext` host-tool capability gating.
|
|
3
|
+
*
|
|
4
|
+
* Two scenarios are verified:
|
|
5
|
+
* - chrome-extension is its own executor and is exempt from the hasNoClient
|
|
6
|
+
* gate (the extension's own popup UI gates commands; there is no SSE
|
|
7
|
+
* interactive approval channel, and chrome-extension turns intentionally
|
|
8
|
+
* run with `hasNoClient: true` because chrome-extension is not in
|
|
9
|
+
* `INTERACTIVE_INTERFACES`).
|
|
10
|
+
* - macos still requires a connected SSE client for interactive approval, so
|
|
11
|
+
* `hasNoClient: true` continues to deny all host tools on macos.
|
|
12
|
+
*
|
|
13
|
+
* The per-capability check (`supportsHostProxy(transport, capability)`) runs
|
|
14
|
+
* first and is authoritative for structural support, so host_bash and
|
|
15
|
+
* host_file_* are filtered out for chrome-extension regardless of the
|
|
16
|
+
* hasNoClient flag.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { describe, expect, test } from "bun:test";
|
|
20
|
+
|
|
21
|
+
import type { SkillProjectionCache } from "../conversation-skill-tools.js";
|
|
22
|
+
import {
|
|
23
|
+
HOST_TOOL_NAMES,
|
|
24
|
+
HOST_TOOL_TO_CAPABILITY,
|
|
25
|
+
isToolActiveForContext,
|
|
26
|
+
type SkillProjectionContext,
|
|
27
|
+
} from "../conversation-tool-setup.js";
|
|
28
|
+
|
|
29
|
+
function makeCtx(
|
|
30
|
+
overrides: Partial<SkillProjectionContext> = {},
|
|
31
|
+
): SkillProjectionContext {
|
|
32
|
+
return {
|
|
33
|
+
skillProjectionState: new Map(),
|
|
34
|
+
skillProjectionCache: {} as SkillProjectionCache,
|
|
35
|
+
coreToolNames: new Set<string>(),
|
|
36
|
+
toolsDisabledDepth: 0,
|
|
37
|
+
...overrides,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("isToolActiveForContext — host tool capability gating", () => {
|
|
42
|
+
// macOS transport: SSE-based interactive approval required.
|
|
43
|
+
test("host_bash is active for macOS with a connected client", () => {
|
|
44
|
+
expect(
|
|
45
|
+
isToolActiveForContext(
|
|
46
|
+
"host_bash",
|
|
47
|
+
makeCtx({ hasNoClient: false, transportInterface: "macos" }),
|
|
48
|
+
),
|
|
49
|
+
).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("host_bash is NOT active for macOS when hasNoClient is true (security invariant)", () => {
|
|
53
|
+
// macOS uses an SSE-based interactive approval channel. Without a
|
|
54
|
+
// connected client the guardian auto-approve path could execute host
|
|
55
|
+
// commands unattended, so host tools must be denied.
|
|
56
|
+
expect(
|
|
57
|
+
isToolActiveForContext(
|
|
58
|
+
"host_bash",
|
|
59
|
+
makeCtx({ hasNoClient: true, transportInterface: "macos" }),
|
|
60
|
+
),
|
|
61
|
+
).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("host_file_read is NOT active for macOS when hasNoClient is true", () => {
|
|
65
|
+
expect(
|
|
66
|
+
isToolActiveForContext(
|
|
67
|
+
"host_file_read",
|
|
68
|
+
makeCtx({ hasNoClient: true, transportInterface: "macos" }),
|
|
69
|
+
),
|
|
70
|
+
).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("host_browser is active for macOS with a connected client", () => {
|
|
74
|
+
expect(
|
|
75
|
+
isToolActiveForContext(
|
|
76
|
+
"host_browser",
|
|
77
|
+
makeCtx({ hasNoClient: false, transportInterface: "macos" }),
|
|
78
|
+
),
|
|
79
|
+
).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("host_browser is NOT active for macOS when hasNoClient is true", () => {
|
|
83
|
+
// macOS requires a client for any host tool — the SSE interactive
|
|
84
|
+
// approval channel must be available regardless of capability.
|
|
85
|
+
expect(
|
|
86
|
+
isToolActiveForContext(
|
|
87
|
+
"host_browser",
|
|
88
|
+
makeCtx({ hasNoClient: true, transportInterface: "macos" }),
|
|
89
|
+
),
|
|
90
|
+
).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// chrome-extension transport: the extension is its own executor.
|
|
94
|
+
test("host_browser is active for chrome-extension even when hasNoClient is true", () => {
|
|
95
|
+
// chrome-extension turns run with `hasNoClient: true` by design because
|
|
96
|
+
// chrome-extension is not in `INTERACTIVE_INTERFACES` — it is not an
|
|
97
|
+
// SSE interactive channel. The extension gates host_browser commands
|
|
98
|
+
// via its own popup UI, so the hasNoClient gate must not filter
|
|
99
|
+
// host_browser out for chrome-extension transports.
|
|
100
|
+
expect(
|
|
101
|
+
isToolActiveForContext(
|
|
102
|
+
"host_browser",
|
|
103
|
+
makeCtx({
|
|
104
|
+
hasNoClient: true,
|
|
105
|
+
transportInterface: "chrome-extension",
|
|
106
|
+
}),
|
|
107
|
+
),
|
|
108
|
+
).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("host_browser is active for chrome-extension when hasNoClient is false", () => {
|
|
112
|
+
expect(
|
|
113
|
+
isToolActiveForContext(
|
|
114
|
+
"host_browser",
|
|
115
|
+
makeCtx({
|
|
116
|
+
hasNoClient: false,
|
|
117
|
+
transportInterface: "chrome-extension",
|
|
118
|
+
}),
|
|
119
|
+
),
|
|
120
|
+
).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("host_bash is NOT active for chrome-extension even when hasNoClient is true", () => {
|
|
124
|
+
// The per-capability check runs first and is authoritative: chrome-extension
|
|
125
|
+
// only supports `host_browser`, so `host_bash` must be filtered out.
|
|
126
|
+
expect(
|
|
127
|
+
isToolActiveForContext(
|
|
128
|
+
"host_bash",
|
|
129
|
+
makeCtx({
|
|
130
|
+
hasNoClient: true,
|
|
131
|
+
transportInterface: "chrome-extension",
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("host_file_read is NOT active for chrome-extension when hasNoClient is true", () => {
|
|
138
|
+
expect(
|
|
139
|
+
isToolActiveForContext(
|
|
140
|
+
"host_file_read",
|
|
141
|
+
makeCtx({
|
|
142
|
+
hasNoClient: true,
|
|
143
|
+
transportInterface: "chrome-extension",
|
|
144
|
+
}),
|
|
145
|
+
),
|
|
146
|
+
).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Backwards-compat fallback: no transport plumbed through.
|
|
150
|
+
test("host_bash falls back to hasNoClient gate when transport is undefined (client connected)", () => {
|
|
151
|
+
// Without a transport interface we cannot run the per-capability check,
|
|
152
|
+
// so we fall back to the coarse-grained `hasNoClient` behavior.
|
|
153
|
+
expect(
|
|
154
|
+
isToolActiveForContext(
|
|
155
|
+
"host_bash",
|
|
156
|
+
makeCtx({ hasNoClient: false, transportInterface: undefined }),
|
|
157
|
+
),
|
|
158
|
+
).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("host_bash falls back to hasNoClient gate when transport is undefined (no client)", () => {
|
|
162
|
+
expect(
|
|
163
|
+
isToolActiveForContext(
|
|
164
|
+
"host_bash",
|
|
165
|
+
makeCtx({ hasNoClient: true, transportInterface: undefined }),
|
|
166
|
+
),
|
|
167
|
+
).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("HOST_TOOL_NAMES derivation", () => {
|
|
172
|
+
test("HOST_TOOL_NAMES is derived from HOST_TOOL_TO_CAPABILITY", () => {
|
|
173
|
+
// Sanity check: every tool in the names set has a capability mapping.
|
|
174
|
+
// This is structurally enforced by the code (HOST_TOOL_NAMES is built
|
|
175
|
+
// from HOST_TOOL_TO_CAPABILITY.keys()), but we test it to make the
|
|
176
|
+
// invariant visible to readers and to catch any regression that
|
|
177
|
+
// splits the two collections back apart.
|
|
178
|
+
for (const name of HOST_TOOL_NAMES) {
|
|
179
|
+
expect(HOST_TOOL_TO_CAPABILITY.has(name)).toBe(true);
|
|
180
|
+
}
|
|
181
|
+
// Cardinality check: the two collections must have the same size so a
|
|
182
|
+
// future addition to HOST_TOOL_NAMES without a matching capability entry
|
|
183
|
+
// (or vice versa) would fail.
|
|
184
|
+
expect(HOST_TOOL_NAMES.size).toBe(HOST_TOOL_TO_CAPABILITY.size);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -24,6 +24,20 @@ const APP_REFRESH_DEBOUNCE_MS = 500;
|
|
|
24
24
|
|
|
25
25
|
export type AppSourceChangeCallback = (appId: string) => void;
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Module-level callback so tool-side-effects can ensure the watcher starts
|
|
29
|
+
* after the apps directory is created (e.g. on first app_create).
|
|
30
|
+
*/
|
|
31
|
+
let ensureWatcherStarted: (() => void) | null = null;
|
|
32
|
+
|
|
33
|
+
export function setEnsureAppSourceWatcher(fn: () => void): void {
|
|
34
|
+
ensureWatcherStarted = fn;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function ensureAppSourceWatcher(): void {
|
|
38
|
+
ensureWatcherStarted?.();
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
/**
|
|
28
42
|
* Resolve app ID from a relative path within the apps directory.
|
|
29
43
|
* Returns null if the path is not an app source file (e.g. dist/, records/).
|
|
@@ -48,12 +62,29 @@ function resolveAppIdFromRelPath(relPath: string): string | null {
|
|
|
48
62
|
|
|
49
63
|
export class AppSourceWatcher {
|
|
50
64
|
private watcher: FSWatcher | null = null;
|
|
65
|
+
private onChange: AppSourceChangeCallback | null = null;
|
|
51
66
|
private debouncer = new DebouncerMap({
|
|
52
67
|
defaultDelayMs: APP_REFRESH_DEBOUNCE_MS,
|
|
53
68
|
maxEntries: 50,
|
|
54
69
|
});
|
|
55
70
|
|
|
56
71
|
start(onChange: AppSourceChangeCallback): void {
|
|
72
|
+
this.onChange = onChange;
|
|
73
|
+
this.tryWatch();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Ensure the watcher is running. Call after app creation so the watcher
|
|
78
|
+
* starts if the apps directory was created after daemon startup.
|
|
79
|
+
*/
|
|
80
|
+
ensureStarted(): void {
|
|
81
|
+
if (this.watcher || !this.onChange) return;
|
|
82
|
+
this.tryWatch();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private tryWatch(): void {
|
|
86
|
+
if (this.watcher) return;
|
|
87
|
+
|
|
57
88
|
let appsDir: string;
|
|
58
89
|
try {
|
|
59
90
|
appsDir = getAppsDir();
|
|
@@ -67,6 +98,9 @@ export class AppSourceWatcher {
|
|
|
67
98
|
return;
|
|
68
99
|
}
|
|
69
100
|
|
|
101
|
+
const onChange = this.onChange;
|
|
102
|
+
if (!onChange) return;
|
|
103
|
+
|
|
70
104
|
try {
|
|
71
105
|
this.watcher = watch(appsDir, { recursive: true }, (_eventType, filename) => {
|
|
72
106
|
if (!filename) return;
|
|
@@ -78,6 +112,7 @@ export class AppSourceWatcher {
|
|
|
78
112
|
onChange(appId);
|
|
79
113
|
});
|
|
80
114
|
});
|
|
115
|
+
log.info("App source watcher started");
|
|
81
116
|
} catch (err) {
|
|
82
117
|
log.warn({ err }, "Failed to watch apps directory; source watching disabled");
|
|
83
118
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
2
2
|
import { isAllowDecision } from "../permissions/types.js";
|
|
3
|
+
import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Reserved pseudo tool name used for context overflow compression approval
|
|
@@ -26,6 +27,10 @@ export async function requestCompressionApproval(
|
|
|
26
27
|
prompter: PermissionPrompter,
|
|
27
28
|
opts?: { signal?: AbortSignal },
|
|
28
29
|
): Promise<CompressionApprovalResult> {
|
|
30
|
+
if (isPermissionControlsV2Enabled()) {
|
|
31
|
+
return { approved: true };
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
const result = await prompter.prompt(
|
|
30
35
|
CONTEXT_OVERFLOW_TOOL_NAME,
|
|
31
36
|
{
|
|
@@ -932,10 +932,25 @@ export async function dispatchAgentEvent(
|
|
|
932
932
|
"assistant_turn",
|
|
933
933
|
deps.reqId,
|
|
934
934
|
);
|
|
935
|
+
|
|
936
|
+
// Format web search results into a human-readable string for the client.
|
|
937
|
+
let resultText = "";
|
|
938
|
+
if (Array.isArray(event.content) && event.content.length > 0) {
|
|
939
|
+
resultText = (event.content as unknown[])
|
|
940
|
+
.filter(
|
|
941
|
+
(r): r is { type: string; title: string; url: string } =>
|
|
942
|
+
typeof r === "object" &&
|
|
943
|
+
r != null &&
|
|
944
|
+
(r as { type?: string }).type === "web_search_result",
|
|
945
|
+
)
|
|
946
|
+
.map((r) => `${r.title}\n${r.url}`)
|
|
947
|
+
.join("\n\n");
|
|
948
|
+
}
|
|
949
|
+
|
|
935
950
|
deps.onEvent({
|
|
936
951
|
type: "tool_result",
|
|
937
|
-
toolName: "",
|
|
938
|
-
result:
|
|
952
|
+
toolName: "web_search",
|
|
953
|
+
result: resultText,
|
|
939
954
|
isError: event.isError,
|
|
940
955
|
conversationId: deps.ctx.conversationId,
|
|
941
956
|
toolUseId: event.toolUseId,
|