@vellumai/assistant 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +184 -69
- package/package.json +41 -41
- package/scripts/generate-openapi.ts +1 -2
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/browser-fill-credential.test.ts +229 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/catalog-files.test.ts +862 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +125 -48
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/context-overflow-approval.test.ts +16 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +1 -1
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-queue.test.ts +45 -2
- package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +2 -2
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/gemini-provider.test.ts +2 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +707 -371
- package/src/__tests__/headless-browser-navigate.test.ts +389 -47
- package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
- package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
- package/src/__tests__/host-bash-proxy.test.ts +150 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
- package/src/__tests__/host-cu-proxy.test.ts +171 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +61 -2
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +101 -1
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/oauth-apps-routes.test.ts +17 -12
- package/src/__tests__/oauth-cli.test.ts +707 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +50 -14
- package/src/__tests__/oauth-store.test.ts +1386 -182
- package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
- package/src/__tests__/onboarding-template-contract.test.ts +75 -57
- package/src/__tests__/openai-provider.test.ts +2 -2
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/require-fresh-approval.test.ts +40 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
- package/src/__tests__/slack-channel-config.test.ts +12 -15
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +72 -1
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/terminal-tools.test.ts +9 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +14 -29
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -6
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +53 -3
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/email.ts +18 -13
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +6 -3
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +200 -36
- package/src/cli/commands/oauth/shared.ts +5 -5
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/contacts/SKILL.md +3 -0
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/env-registry.ts +14 -0
- package/src/config/env.ts +21 -0
- package/src/config/feature-flag-registry.json +44 -5
- package/src/config/loader.ts +56 -1
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +46 -5
- package/src/config/schemas/host-browser.ts +66 -0
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +8 -0
- package/src/config/types.ts +0 -1
- package/src/context/post-turn-tool-result-truncation.ts +176 -0
- package/src/context/window-manager.ts +19 -1
- package/src/credential-execution/approval-bridge.ts +49 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/context-overflow-approval.ts +5 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +58 -24
- package/src/daemon/conversation-attachments.ts +40 -0
- package/src/daemon/conversation-process.ts +48 -1
- package/src/daemon/conversation-runtime-assembly.ts +118 -36
- package/src/daemon/conversation-surfaces.ts +37 -36
- package/src/daemon/conversation-tool-setup.ts +74 -8
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +226 -8
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -140
- package/src/daemon/handlers/shared.ts +58 -0
- package/src/daemon/handlers/skills.ts +232 -37
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +191 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +65 -11
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +55 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -5
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +92 -12
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +5 -24
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +23 -0
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +176 -17
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/retriever.ts +40 -22
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/llm-usage-store.ts +45 -4
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +1 -0
- package/src/memory/schema/oauth.ts +18 -13
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +8 -8
- package/src/oauth/byo-connection.ts +7 -7
- package/src/oauth/connect-orchestrator.ts +23 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +16 -16
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +214 -100
- package/src/oauth/platform-connection.test.ts +3 -3
- package/src/oauth/platform-connection.ts +4 -4
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +126 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -1
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -105
- package/src/providers/anthropic/client.ts +1 -0
- package/src/providers/types.ts +1 -1
- package/src/runtime/AGENTS.md +23 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/assistant-event-hub.ts +2 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +6 -7
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/chrome-extension-registry.ts +332 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +425 -70
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
- package/src/runtime/migrations/migration-transport.ts +6 -0
- package/src/runtime/migrations/migration-wizard.ts +22 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
- package/src/runtime/migrations/vbundle-importer.ts +55 -5
- package/src/runtime/pending-interactions.ts +29 -13
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/browser-cdp-routes.ts +229 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
- package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +301 -27
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/host-browser-routes.ts +279 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-routes.ts +259 -16
- package/src/runtime/routes/log-export-routes.ts +42 -22
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +87 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +9 -97
- package/src/runtime/routes/skills-routes.ts +52 -2
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/usage-routes.ts +8 -7
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/schedule/scheduler.ts +7 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +42 -25
- package/src/security/secure-keys.ts +118 -25
- package/src/security/token-manager.ts +23 -10
- package/src/skills/catalog-files.ts +492 -0
- package/src/subagent/manager.ts +131 -26
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +645 -340
- package/src/tools/browser/browser-manager.ts +36 -12
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
- package/src/tools/browser/cdp-client/errors.ts +34 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
- package/src/tools/browser/cdp-client/factory.ts +204 -0
- package/src/tools/browser/cdp-client/index.ts +14 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +52 -0
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/permission-checker.ts +77 -82
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -0
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/shell.ts +21 -16
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -0
- package/src/util/platform.ts +14 -19
- package/src/workspace/top-level-renderer.ts +19 -1
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/tools/browser/chrome-cdp.ts +0 -239
- package/src/tools/system/set-permission-mode.ts +0 -103
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { v4 as uuid } from "uuid";
|
|
4
|
+
|
|
5
|
+
mock.module("../util/logger.js", () => ({
|
|
6
|
+
getLogger: () =>
|
|
7
|
+
new Proxy({} as Record<string, unknown>, {
|
|
8
|
+
get: () => () => {},
|
|
9
|
+
}),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
import { maybeEnqueueConversationStartersJob } from "../memory/conversation-starters-cadence.js";
|
|
13
|
+
import { getSqlite, initializeDb } from "../memory/db.js";
|
|
14
|
+
|
|
15
|
+
initializeDb();
|
|
16
|
+
|
|
17
|
+
function clearTables() {
|
|
18
|
+
getSqlite().run("DELETE FROM memory_graph_nodes");
|
|
19
|
+
getSqlite().run("DELETE FROM memory_jobs");
|
|
20
|
+
getSqlite().run("DELETE FROM memory_checkpoints");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function insertMemoryNode(scopeId = "default") {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
getSqlite().run(
|
|
26
|
+
`INSERT INTO memory_graph_nodes (
|
|
27
|
+
id, content, type, created, last_accessed, last_consolidated,
|
|
28
|
+
emotional_charge, fidelity, confidence, significance,
|
|
29
|
+
stability, reinforcement_count, last_reinforced,
|
|
30
|
+
source_conversations, source_type, scope_id
|
|
31
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'vivid', 0.8, 0.5, 14, 0, ?, '[]', 'inferred', ?)`,
|
|
32
|
+
[
|
|
33
|
+
uuid(),
|
|
34
|
+
"test statement",
|
|
35
|
+
"semantic",
|
|
36
|
+
now,
|
|
37
|
+
now,
|
|
38
|
+
now,
|
|
39
|
+
'{"valence":0,"intensity":0.1,"decayCurve":"linear","decayRate":0.05,"originalIntensity":0.1}',
|
|
40
|
+
now,
|
|
41
|
+
scopeId,
|
|
42
|
+
],
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function setCheckpoint(key: string, value: string) {
|
|
47
|
+
getSqlite().run(
|
|
48
|
+
`INSERT OR REPLACE INTO memory_checkpoints (key, value, updated_at) VALUES (?, ?, ?)`,
|
|
49
|
+
[key, value, Date.now()],
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getPendingJobs(): Array<{ type: string }> {
|
|
54
|
+
return getSqlite()
|
|
55
|
+
.prepare(
|
|
56
|
+
`SELECT type FROM memory_jobs WHERE type = 'generate_conversation_starters' AND status = 'pending'`,
|
|
57
|
+
)
|
|
58
|
+
.all() as Array<{ type: string }>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
clearTables();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("maybeEnqueueConversationStartersJob", () => {
|
|
66
|
+
test("no-op when zero memory nodes", () => {
|
|
67
|
+
maybeEnqueueConversationStartersJob("default");
|
|
68
|
+
expect(getPendingJobs()).toHaveLength(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("enqueues when threshold exceeded (<=10 nodes, threshold=1)", () => {
|
|
72
|
+
insertMemoryNode();
|
|
73
|
+
insertMemoryNode();
|
|
74
|
+
|
|
75
|
+
maybeEnqueueConversationStartersJob("default");
|
|
76
|
+
expect(getPendingJobs()).toHaveLength(1);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("no-op when delta below threshold", () => {
|
|
80
|
+
for (let i = 0; i < 5; i++) insertMemoryNode();
|
|
81
|
+
|
|
82
|
+
// Set checkpoint to current count — no new items since last gen
|
|
83
|
+
setCheckpoint("conversation_starters:item_count_at_last_gen:default", "5");
|
|
84
|
+
|
|
85
|
+
maybeEnqueueConversationStartersJob("default");
|
|
86
|
+
expect(getPendingJobs()).toHaveLength(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("uses higher threshold for larger memory counts (>50 nodes, threshold=10)", () => {
|
|
90
|
+
for (let i = 0; i < 55; i++) insertMemoryNode();
|
|
91
|
+
|
|
92
|
+
// Set checkpoint so delta is only 4 (below threshold of 10)
|
|
93
|
+
setCheckpoint(
|
|
94
|
+
"conversation_starters:item_count_at_last_gen:default",
|
|
95
|
+
"51",
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
maybeEnqueueConversationStartersJob("default");
|
|
99
|
+
expect(getPendingJobs()).toHaveLength(0);
|
|
100
|
+
|
|
101
|
+
// Add more to exceed threshold
|
|
102
|
+
for (let i = 0; i < 6; i++) insertMemoryNode();
|
|
103
|
+
|
|
104
|
+
maybeEnqueueConversationStartersJob("default");
|
|
105
|
+
expect(getPendingJobs()).toHaveLength(1);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("dedup prevents double-enqueue", () => {
|
|
109
|
+
insertMemoryNode();
|
|
110
|
+
insertMemoryNode();
|
|
111
|
+
|
|
112
|
+
maybeEnqueueConversationStartersJob("default");
|
|
113
|
+
expect(getPendingJobs()).toHaveLength(1);
|
|
114
|
+
|
|
115
|
+
// Call again — should not create a second job
|
|
116
|
+
maybeEnqueueConversationStartersJob("default");
|
|
117
|
+
expect(getPendingJobs()).toHaveLength(1);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("enqueues when active memory count drops below the last-generation checkpoint", () => {
|
|
121
|
+
// Start with 5 nodes and set checkpoint to 5
|
|
122
|
+
for (let i = 0; i < 5; i++) insertMemoryNode();
|
|
123
|
+
setCheckpoint("conversation_starters:item_count_at_last_gen:default", "5");
|
|
124
|
+
|
|
125
|
+
// Simulate pruning: mark 3 nodes as gone (reducing totalActive to 2)
|
|
126
|
+
const ids = (
|
|
127
|
+
getSqlite()
|
|
128
|
+
.prepare(`SELECT id FROM memory_graph_nodes LIMIT 3`)
|
|
129
|
+
.all() as Array<{ id: string }>
|
|
130
|
+
).map((r) => r.id);
|
|
131
|
+
for (const id of ids) {
|
|
132
|
+
getSqlite().run(
|
|
133
|
+
`UPDATE memory_graph_nodes SET fidelity = 'gone' WHERE id = ?`,
|
|
134
|
+
[id],
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The checkpoint is now ahead of the active memory count. This should
|
|
139
|
+
// enqueue a refresh immediately so stale starters can recover.
|
|
140
|
+
maybeEnqueueConversationStartersJob("default");
|
|
141
|
+
expect(getPendingJobs()).toHaveLength(1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("scopes are independent", () => {
|
|
145
|
+
insertMemoryNode("scope-a");
|
|
146
|
+
insertMemoryNode("scope-b");
|
|
147
|
+
|
|
148
|
+
maybeEnqueueConversationStartersJob("scope-a");
|
|
149
|
+
maybeEnqueueConversationStartersJob("scope-b");
|
|
150
|
+
|
|
151
|
+
const jobs = getSqlite()
|
|
152
|
+
.prepare(
|
|
153
|
+
`SELECT payload FROM memory_jobs WHERE type = 'generate_conversation_starters' AND status = 'pending'`,
|
|
154
|
+
)
|
|
155
|
+
.all() as Array<{ payload: string }>;
|
|
156
|
+
|
|
157
|
+
expect(jobs).toHaveLength(2);
|
|
158
|
+
const payloads = jobs.map((j) => JSON.parse(j.payload).scopeId).sort();
|
|
159
|
+
expect(payloads).toEqual(["scope-a", "scope-b"]);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
1
2
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
3
|
|
|
4
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
5
|
+
|
|
3
6
|
mock.module("../util/logger.js", () => ({
|
|
4
7
|
getLogger: () =>
|
|
5
8
|
new Proxy({} as Record<string, unknown>, {
|
|
@@ -19,16 +22,28 @@ import {
|
|
|
19
22
|
createConversation,
|
|
20
23
|
deleteLastExchange,
|
|
21
24
|
getConversation,
|
|
25
|
+
getConversationHostAccess,
|
|
22
26
|
getConversationMemoryScopeId,
|
|
23
27
|
getConversationType,
|
|
24
28
|
getMessages,
|
|
29
|
+
updateConversationHostAccess,
|
|
25
30
|
} from "../memory/conversation-crud.js";
|
|
26
31
|
import { isLastUserMessageToolResult } from "../memory/conversation-queries.js";
|
|
27
32
|
import { getDb, initializeDb } from "../memory/db.js";
|
|
33
|
+
import { getSqliteFrom } from "../memory/db-connection.js";
|
|
34
|
+
import { migrateConversationHostAccess } from "../memory/migrations/217-conversation-host-access.js";
|
|
35
|
+
import * as schema from "../memory/schema.js";
|
|
28
36
|
|
|
29
37
|
// Initialize db once before all tests
|
|
30
38
|
initializeDb();
|
|
31
39
|
|
|
40
|
+
function createMigrationTestDb() {
|
|
41
|
+
const sqlite = new Database(":memory:");
|
|
42
|
+
sqlite.exec("PRAGMA journal_mode=WAL");
|
|
43
|
+
sqlite.exec("PRAGMA foreign_keys = ON");
|
|
44
|
+
return drizzle(sqlite, { schema });
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
describe("deleteLastExchange", () => {
|
|
33
48
|
beforeEach(() => {
|
|
34
49
|
// Reset database between tests by dropping and recreating tables
|
|
@@ -406,6 +421,7 @@ describe("conversation metadata defaults", () => {
|
|
|
406
421
|
expect(loaded).not.toBeNull();
|
|
407
422
|
expect(loaded!.conversationType).toBe("standard");
|
|
408
423
|
expect(loaded!.memoryScopeId).toBe("default");
|
|
424
|
+
expect(loaded!.hostAccess).toBe(0);
|
|
409
425
|
});
|
|
410
426
|
|
|
411
427
|
test("existing conversations without explicit values get defaults via migration", () => {
|
|
@@ -422,6 +438,7 @@ describe("conversation metadata defaults", () => {
|
|
|
422
438
|
expect(loaded).not.toBeNull();
|
|
423
439
|
expect(loaded!.conversationType).toBe("standard");
|
|
424
440
|
expect(loaded!.memoryScopeId).toBe("default");
|
|
441
|
+
expect(loaded!.hostAccess).toBe(0);
|
|
425
442
|
});
|
|
426
443
|
});
|
|
427
444
|
|
|
@@ -506,6 +523,184 @@ describe("conversation metadata read helpers", () => {
|
|
|
506
523
|
test("getConversationMemoryScopeId returns default for missing conversation", () => {
|
|
507
524
|
expect(getConversationMemoryScopeId("nonexistent-id")).toBe("default");
|
|
508
525
|
});
|
|
526
|
+
|
|
527
|
+
test("getConversationHostAccess returns false by default", () => {
|
|
528
|
+
const conv = createConversation("test");
|
|
529
|
+
expect(getConversationHostAccess(conv.id)).toBe(false);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("getConversationHostAccess returns false for missing conversation", () => {
|
|
533
|
+
expect(getConversationHostAccess("nonexistent-id")).toBe(false);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
describe("conversation host access persistence", () => {
|
|
538
|
+
beforeEach(() => {
|
|
539
|
+
const db = getDb();
|
|
540
|
+
db.run(`DELETE FROM messages`);
|
|
541
|
+
db.run(`DELETE FROM conversations`);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("new conversations default host access to disabled", () => {
|
|
545
|
+
const conv = createConversation("test");
|
|
546
|
+
const loaded = getConversation(conv.id);
|
|
547
|
+
|
|
548
|
+
expect(conv.hostAccess).toBe(0);
|
|
549
|
+
expect(loaded).not.toBeNull();
|
|
550
|
+
expect(loaded!.hostAccess).toBe(0);
|
|
551
|
+
expect(getConversationHostAccess(conv.id)).toBe(false);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test("updateConversationHostAccess persists mutations", () => {
|
|
555
|
+
const conv = createConversation("test");
|
|
556
|
+
|
|
557
|
+
updateConversationHostAccess(conv.id, true);
|
|
558
|
+
expect(getConversationHostAccess(conv.id)).toBe(true);
|
|
559
|
+
expect(getConversation(conv.id)?.hostAccess).toBe(1);
|
|
560
|
+
|
|
561
|
+
updateConversationHostAccess(conv.id, false);
|
|
562
|
+
expect(getConversationHostAccess(conv.id)).toBe(false);
|
|
563
|
+
expect(getConversation(conv.id)?.hostAccess).toBe(0);
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
describe("conversation host access migration", () => {
|
|
568
|
+
function bootstrapPreHostAccessConversations(raw: Database): void {
|
|
569
|
+
raw.exec(/*sql*/ `
|
|
570
|
+
CREATE TABLE memory_checkpoints (
|
|
571
|
+
key TEXT PRIMARY KEY,
|
|
572
|
+
value TEXT NOT NULL,
|
|
573
|
+
updated_at INTEGER NOT NULL
|
|
574
|
+
)
|
|
575
|
+
`);
|
|
576
|
+
|
|
577
|
+
raw.exec(/*sql*/ `
|
|
578
|
+
CREATE TABLE conversations (
|
|
579
|
+
id TEXT PRIMARY KEY,
|
|
580
|
+
title TEXT,
|
|
581
|
+
created_at INTEGER NOT NULL,
|
|
582
|
+
updated_at INTEGER NOT NULL,
|
|
583
|
+
total_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
584
|
+
total_output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
585
|
+
total_estimated_cost REAL NOT NULL DEFAULT 0,
|
|
586
|
+
context_summary TEXT,
|
|
587
|
+
context_compacted_message_count INTEGER NOT NULL DEFAULT 0,
|
|
588
|
+
context_compacted_at INTEGER,
|
|
589
|
+
conversation_type TEXT NOT NULL DEFAULT 'standard',
|
|
590
|
+
source TEXT NOT NULL DEFAULT 'user',
|
|
591
|
+
memory_scope_id TEXT NOT NULL DEFAULT 'default',
|
|
592
|
+
origin_channel TEXT,
|
|
593
|
+
origin_interface TEXT,
|
|
594
|
+
fork_parent_conversation_id TEXT,
|
|
595
|
+
fork_parent_message_id TEXT,
|
|
596
|
+
is_auto_title INTEGER NOT NULL DEFAULT 1,
|
|
597
|
+
schedule_job_id TEXT,
|
|
598
|
+
last_message_at INTEGER
|
|
599
|
+
)
|
|
600
|
+
`);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
test("migration adds host access with disabled default for existing rows", () => {
|
|
604
|
+
const db = createMigrationTestDb();
|
|
605
|
+
const raw = getSqliteFrom(db);
|
|
606
|
+
const now = Date.now();
|
|
607
|
+
|
|
608
|
+
bootstrapPreHostAccessConversations(raw);
|
|
609
|
+
raw.exec(/*sql*/ `
|
|
610
|
+
INSERT INTO conversations (
|
|
611
|
+
id,
|
|
612
|
+
title,
|
|
613
|
+
created_at,
|
|
614
|
+
updated_at,
|
|
615
|
+
conversation_type,
|
|
616
|
+
source,
|
|
617
|
+
memory_scope_id,
|
|
618
|
+
is_auto_title
|
|
619
|
+
) VALUES (
|
|
620
|
+
'conv-upgrade',
|
|
621
|
+
'Existing conversation',
|
|
622
|
+
${now},
|
|
623
|
+
${now},
|
|
624
|
+
'standard',
|
|
625
|
+
'user',
|
|
626
|
+
'default',
|
|
627
|
+
1
|
|
628
|
+
)
|
|
629
|
+
`);
|
|
630
|
+
|
|
631
|
+
migrateConversationHostAccess(db);
|
|
632
|
+
|
|
633
|
+
const row = raw
|
|
634
|
+
.query(
|
|
635
|
+
`SELECT id, title, host_access FROM conversations WHERE id = 'conv-upgrade'`,
|
|
636
|
+
)
|
|
637
|
+
.get() as {
|
|
638
|
+
id: string;
|
|
639
|
+
title: string | null;
|
|
640
|
+
host_access: number;
|
|
641
|
+
} | null;
|
|
642
|
+
|
|
643
|
+
expect(row).toEqual({
|
|
644
|
+
id: "conv-upgrade",
|
|
645
|
+
title: "Existing conversation",
|
|
646
|
+
host_access: 0,
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
const checkpoint = raw
|
|
650
|
+
.query(
|
|
651
|
+
`SELECT value FROM memory_checkpoints WHERE key = 'migration_conversation_host_access_v1'`,
|
|
652
|
+
)
|
|
653
|
+
.get() as { value: string } | null;
|
|
654
|
+
expect(checkpoint?.value).toBe("1");
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("re-running the migration preserves existing host access values", () => {
|
|
658
|
+
const db = createMigrationTestDb();
|
|
659
|
+
const raw = getSqliteFrom(db);
|
|
660
|
+
const now = Date.now();
|
|
661
|
+
|
|
662
|
+
bootstrapPreHostAccessConversations(raw);
|
|
663
|
+
raw.exec(/*sql*/ `
|
|
664
|
+
INSERT INTO conversations (
|
|
665
|
+
id,
|
|
666
|
+
title,
|
|
667
|
+
created_at,
|
|
668
|
+
updated_at,
|
|
669
|
+
conversation_type,
|
|
670
|
+
source,
|
|
671
|
+
memory_scope_id,
|
|
672
|
+
is_auto_title
|
|
673
|
+
) VALUES (
|
|
674
|
+
'conv-rerun',
|
|
675
|
+
'Existing conversation',
|
|
676
|
+
${now},
|
|
677
|
+
${now},
|
|
678
|
+
'standard',
|
|
679
|
+
'user',
|
|
680
|
+
'default',
|
|
681
|
+
1
|
|
682
|
+
)
|
|
683
|
+
`);
|
|
684
|
+
|
|
685
|
+
migrateConversationHostAccess(db);
|
|
686
|
+
raw.exec(
|
|
687
|
+
`UPDATE conversations SET host_access = 1 WHERE id = 'conv-rerun'`,
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
expect(() => migrateConversationHostAccess(db)).not.toThrow();
|
|
691
|
+
|
|
692
|
+
const row = raw
|
|
693
|
+
.query(`SELECT host_access FROM conversations WHERE id = 'conv-rerun'`)
|
|
694
|
+
.get() as { host_access: number } | null;
|
|
695
|
+
const checkpoint = raw
|
|
696
|
+
.query(
|
|
697
|
+
`SELECT value FROM memory_checkpoints WHERE key = 'migration_conversation_host_access_v1'`,
|
|
698
|
+
)
|
|
699
|
+
.get() as { value: string } | null;
|
|
700
|
+
|
|
701
|
+
expect(row).toEqual({ host_access: 1 });
|
|
702
|
+
expect(checkpoint?.value).toBe("1");
|
|
703
|
+
});
|
|
509
704
|
});
|
|
510
705
|
|
|
511
706
|
// ---------------------------------------------------------------------------
|
|
@@ -269,6 +269,199 @@ describe("Conversation workspace cache state", () => {
|
|
|
269
269
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
270
270
|
});
|
|
271
271
|
|
|
272
|
+
test("renders client-reported host env when set on the conversation", () => {
|
|
273
|
+
conversation.hostHomeDir = "/Users/alice";
|
|
274
|
+
conversation.hostUsername = "alice";
|
|
275
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
276
|
+
|
|
277
|
+
const block = conversation.getWorkspaceTopLevelContext();
|
|
278
|
+
expect(block).not.toBeNull();
|
|
279
|
+
expect(block!).toContain("Host home directory: /Users/alice");
|
|
280
|
+
expect(block!).toContain("Host username: alice");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("falls back to daemon os info when client host env is absent", async () => {
|
|
284
|
+
const { homedir, userInfo } = await import("node:os");
|
|
285
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
286
|
+
|
|
287
|
+
const block = conversation.getWorkspaceTopLevelContext();
|
|
288
|
+
expect(block).not.toBeNull();
|
|
289
|
+
expect(block!).toContain(`Host home directory: ${homedir()}`);
|
|
290
|
+
expect(block!).toContain(`Host username: ${userInfo().username}`);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("re-renders with updated host env after marking dirty", () => {
|
|
294
|
+
conversation.hostHomeDir = "/Users/alice";
|
|
295
|
+
conversation.hostUsername = "alice";
|
|
296
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
297
|
+
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
298
|
+
"Host home directory: /Users/alice",
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
conversation.hostHomeDir = "/Users/bob";
|
|
302
|
+
conversation.hostUsername = "bob";
|
|
303
|
+
conversation.markWorkspaceTopLevelDirty();
|
|
304
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
305
|
+
|
|
306
|
+
const block = conversation.getWorkspaceTopLevelContext();
|
|
307
|
+
expect(block).not.toBeNull();
|
|
308
|
+
expect(block!).toContain("Host home directory: /Users/bob");
|
|
309
|
+
expect(block!).toContain("Host username: bob");
|
|
310
|
+
expect(block!).not.toContain("Host home directory: /Users/alice");
|
|
311
|
+
expect(block!).not.toContain("Host username: alice");
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("falls back to os info after clearing macOS host env (cross-interface reuse)", async () => {
|
|
315
|
+
const { homedir, userInfo } = await import("node:os");
|
|
316
|
+
|
|
317
|
+
// Simulate a macOS turn populating host env.
|
|
318
|
+
conversation.hostHomeDir = "/Users/alice";
|
|
319
|
+
conversation.hostUsername = "alice";
|
|
320
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
321
|
+
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
322
|
+
"Host home directory: /Users/alice",
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
// Simulate a subsequent non-macOS turn (iOS, CLI, channel) on the same
|
|
326
|
+
// conversation clearing the host env — without the clear, the stale
|
|
327
|
+
// macOS paths would leak into the next render.
|
|
328
|
+
conversation.hostHomeDir = undefined;
|
|
329
|
+
conversation.hostUsername = undefined;
|
|
330
|
+
conversation.markWorkspaceTopLevelDirty();
|
|
331
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
332
|
+
|
|
333
|
+
const block = conversation.getWorkspaceTopLevelContext();
|
|
334
|
+
expect(block).not.toBeNull();
|
|
335
|
+
expect(block!).toContain(`Host home directory: ${homedir()}`);
|
|
336
|
+
expect(block!).toContain(`Host username: ${userInfo().username}`);
|
|
337
|
+
expect(block!).not.toContain("Host home directory: /Users/alice");
|
|
338
|
+
expect(block!).not.toContain("Host username: alice");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// -------------------------------------------------------------------------
|
|
342
|
+
// applyHostEnvFromTransport — capability-gated setter
|
|
343
|
+
// -------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
test("applyHostEnvFromTransport populates fields for host-proxy transports", () => {
|
|
346
|
+
conversation.applyHostEnvFromTransport({
|
|
347
|
+
channelId: "vellum",
|
|
348
|
+
interfaceId: "macos",
|
|
349
|
+
hostHomeDir: "/Users/alice",
|
|
350
|
+
hostUsername: "alice",
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
expect(conversation.hostHomeDir).toBe("/Users/alice");
|
|
354
|
+
expect(conversation.hostUsername).toBe("alice");
|
|
355
|
+
expect(conversation.isWorkspaceTopLevelDirty()).toBe(true);
|
|
356
|
+
|
|
357
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
358
|
+
const block = conversation.getWorkspaceTopLevelContext();
|
|
359
|
+
expect(block!).toContain("Host home directory: /Users/alice");
|
|
360
|
+
expect(block!).toContain("Host username: alice");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("applyHostEnvFromTransport clears fields for non-host-proxy transports", () => {
|
|
364
|
+
// Seed with a host-proxy turn.
|
|
365
|
+
conversation.applyHostEnvFromTransport({
|
|
366
|
+
channelId: "vellum",
|
|
367
|
+
interfaceId: "macos",
|
|
368
|
+
hostHomeDir: "/Users/alice",
|
|
369
|
+
hostUsername: "alice",
|
|
370
|
+
});
|
|
371
|
+
expect(conversation.hostHomeDir).toBe("/Users/alice");
|
|
372
|
+
|
|
373
|
+
// Apply a non-host-proxy transport — should clear the stored values so
|
|
374
|
+
// the next render doesn't leak them from a cross-interface reuse.
|
|
375
|
+
conversation.applyHostEnvFromTransport({
|
|
376
|
+
channelId: "vellum",
|
|
377
|
+
interfaceId: "ios",
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect(conversation.hostHomeDir).toBeUndefined();
|
|
381
|
+
expect(conversation.hostUsername).toBeUndefined();
|
|
382
|
+
expect(conversation.isWorkspaceTopLevelDirty()).toBe(true);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("applyHostEnvFromTransport clears fields for chrome-extension (browser-only)", () => {
|
|
386
|
+
// chrome-extension supports only host_browser — the no-arg supportsHostProxy
|
|
387
|
+
// returns false for it, so the gate treats it as a non-host-proxy transport
|
|
388
|
+
// for the purposes of host env (no local filesystem to address).
|
|
389
|
+
conversation.applyHostEnvFromTransport({
|
|
390
|
+
channelId: "vellum",
|
|
391
|
+
interfaceId: "macos",
|
|
392
|
+
hostHomeDir: "/Users/alice",
|
|
393
|
+
hostUsername: "alice",
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
conversation.applyHostEnvFromTransport({
|
|
397
|
+
channelId: "vellum",
|
|
398
|
+
interfaceId: "chrome-extension",
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
expect(conversation.hostHomeDir).toBeUndefined();
|
|
402
|
+
expect(conversation.hostUsername).toBeUndefined();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("applyHostEnvFromTransport handles transport with no interfaceId", () => {
|
|
406
|
+
// Seed and then apply a transport without an interfaceId (legacy/channel
|
|
407
|
+
// paths may omit it). The gate should clear any stored host env.
|
|
408
|
+
conversation.applyHostEnvFromTransport({
|
|
409
|
+
channelId: "vellum",
|
|
410
|
+
interfaceId: "macos",
|
|
411
|
+
hostHomeDir: "/Users/alice",
|
|
412
|
+
hostUsername: "alice",
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
conversation.applyHostEnvFromTransport({
|
|
416
|
+
channelId: "vellum",
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
expect(conversation.hostHomeDir).toBeUndefined();
|
|
420
|
+
expect(conversation.hostUsername).toBeUndefined();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("applyHostEnvFromTransport does not mark dirty when values are unchanged", () => {
|
|
424
|
+
conversation.applyHostEnvFromTransport({
|
|
425
|
+
channelId: "vellum",
|
|
426
|
+
interfaceId: "macos",
|
|
427
|
+
hostHomeDir: "/Users/alice",
|
|
428
|
+
hostUsername: "alice",
|
|
429
|
+
});
|
|
430
|
+
// Render once so the dirty flag clears.
|
|
431
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
432
|
+
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
433
|
+
|
|
434
|
+
// Re-apply the same values — dirty flag should remain false so we don't
|
|
435
|
+
// thrash the cached workspace block on every message.
|
|
436
|
+
conversation.applyHostEnvFromTransport({
|
|
437
|
+
channelId: "vellum",
|
|
438
|
+
interfaceId: "macos",
|
|
439
|
+
hostHomeDir: "/Users/alice",
|
|
440
|
+
hostUsername: "alice",
|
|
441
|
+
});
|
|
442
|
+
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("applyHostEnvFromTransport marks dirty when macOS values change", () => {
|
|
446
|
+
conversation.applyHostEnvFromTransport({
|
|
447
|
+
channelId: "vellum",
|
|
448
|
+
interfaceId: "macos",
|
|
449
|
+
hostHomeDir: "/Users/alice",
|
|
450
|
+
hostUsername: "alice",
|
|
451
|
+
});
|
|
452
|
+
conversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
453
|
+
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
454
|
+
|
|
455
|
+
// New values — should mark dirty so the next render picks them up.
|
|
456
|
+
conversation.applyHostEnvFromTransport({
|
|
457
|
+
channelId: "vellum",
|
|
458
|
+
interfaceId: "macos",
|
|
459
|
+
hostHomeDir: "/Users/bob",
|
|
460
|
+
hostUsername: "bob",
|
|
461
|
+
});
|
|
462
|
+
expect(conversation.isWorkspaceTopLevelDirty()).toBe(true);
|
|
463
|
+
});
|
|
464
|
+
|
|
272
465
|
test("workspace hints follow the resolved legacy directory when canonical is absent", () => {
|
|
273
466
|
const workspaceRoot = mkdtempSync(
|
|
274
467
|
join(tmpdir(), "conversation-workspace-cache-state-"),
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 7. Error handling: record_grant RPC failure returns error outcome.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { describe, expect, test } from "bun:test";
|
|
15
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
16
16
|
|
|
17
17
|
import type {
|
|
18
18
|
ApprovalRequired,
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
RecordGrantResponse,
|
|
23
23
|
} from "@vellumai/ces-contracts";
|
|
24
24
|
|
|
25
|
+
import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
|
|
25
26
|
import { bridgeCesApproval } from "../credential-execution/approval-bridge.js";
|
|
26
27
|
import type { CesClient } from "../credential-execution/client.js";
|
|
27
28
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
@@ -166,6 +167,36 @@ function makeCesClient(
|
|
|
166
167
|
// ---------------------------------------------------------------------------
|
|
167
168
|
|
|
168
169
|
describe("CES approval bridge", () => {
|
|
170
|
+
beforeEach(() => {
|
|
171
|
+
_setOverridesForTesting({});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("suppresses deterministic CES approval prompts and auto-allows under v2", async () => {
|
|
175
|
+
_setOverridesForTesting({ "permission-controls-v2": true });
|
|
176
|
+
|
|
177
|
+
const prompter = makePrompter("allow");
|
|
178
|
+
const cesClient = makeCesClient();
|
|
179
|
+
|
|
180
|
+
const result = await bridgeCesApproval(
|
|
181
|
+
makeApprovalRequired(),
|
|
182
|
+
prompter,
|
|
183
|
+
cesClient,
|
|
184
|
+
{ isInteractive: true, conversationId: "session-1" },
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(result.outcome).toBe("approved");
|
|
188
|
+
if (result.outcome === "approved") {
|
|
189
|
+
expect(result.userDecision).toBe("allow");
|
|
190
|
+
expect(result.grantId).toBe("grant-001");
|
|
191
|
+
}
|
|
192
|
+
expect(prompter.promptCalls).toHaveLength(0);
|
|
193
|
+
expect(cesClient.recordGrantCalls).toHaveLength(1);
|
|
194
|
+
expect(cesClient.recordGrantCalls[0]?.decision.decision).toBe("approved");
|
|
195
|
+
expect(cesClient.recordGrantCalls[0]?.decision.grantType).toBe(
|
|
196
|
+
"allow_once",
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
169
200
|
describe("single-use approval", () => {
|
|
170
201
|
test("allow decision commits grant to CES and returns grantId", async () => {
|
|
171
202
|
const prompter = makePrompter("allow");
|
|
@@ -194,6 +194,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
194
194
|
"oauth/token-persistence.ts", // OAuth token persistence (set/delete tokens)
|
|
195
195
|
"oauth/connection-resolver.ts", // resolve OAuthConnection from oauth-store (access_token lookup)
|
|
196
196
|
"runtime/routes/secret-routes.ts", // HTTP secret management routes (set/delete secrets)
|
|
197
|
+
"runtime/routes/migration-routes.ts", // migration import credential restore
|
|
197
198
|
"daemon/conversation-messaging.ts", // credential storage during session messaging
|
|
198
199
|
"runtime/routes/settings-routes.ts", // settings routes OAuth credential lookup (client_secret)
|
|
199
200
|
"oauth/oauth-store.ts", // OAuth provider disconnect (delete stored tokens)
|
|
@@ -60,11 +60,11 @@ let slackChannelConfigCalls: Array<{
|
|
|
60
60
|
}> = [];
|
|
61
61
|
|
|
62
62
|
mock.module("../oauth/manual-token-connection.js", () => ({
|
|
63
|
-
syncManualTokenConnection: async (
|
|
63
|
+
syncManualTokenConnection: async (provider: string) => {
|
|
64
64
|
const { credentialKey } = await import("../security/credential-key.js");
|
|
65
65
|
const { getSecureKeyAsync } = await import("../security/secure-keys.js");
|
|
66
66
|
|
|
67
|
-
if (
|
|
67
|
+
if (provider === "slack_channel") {
|
|
68
68
|
const hasBotToken = !!(await getSecureKeyAsync(
|
|
69
69
|
credentialKey("slack_channel", "bot_token"),
|
|
70
70
|
));
|
|
@@ -72,9 +72,9 @@ mock.module("../oauth/manual-token-connection.js", () => ({
|
|
|
72
72
|
credentialKey("slack_channel", "app_token"),
|
|
73
73
|
));
|
|
74
74
|
if (hasBotToken && hasAppToken) {
|
|
75
|
-
manualConnectionStore[
|
|
75
|
+
manualConnectionStore[provider] = "active";
|
|
76
76
|
} else {
|
|
77
|
-
delete manualConnectionStore[
|
|
77
|
+
delete manualConnectionStore[provider];
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
},
|