@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
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// Memory Graph — Data access layer
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
-
import { and, desc, eq, inArray, sql } from "drizzle-orm";
|
|
5
|
+
import { and, desc, eq, inArray, or, sql } from "drizzle-orm";
|
|
6
6
|
import { v4 as uuid } from "uuid";
|
|
7
7
|
|
|
8
8
|
import { getDb } from "../db.js";
|
|
9
|
+
import { enqueueMemoryJob } from "../jobs-store.js";
|
|
9
10
|
import {
|
|
10
11
|
memoryGraphEdges,
|
|
11
12
|
memoryGraphNodeEdits,
|
|
@@ -270,7 +271,14 @@ export function updateNode(
|
|
|
270
271
|
|
|
271
272
|
export function deleteNode(id: string): void {
|
|
272
273
|
const db = getDb();
|
|
273
|
-
db.
|
|
274
|
+
db.update(memoryGraphNodes)
|
|
275
|
+
.set({ fidelity: "gone", lastAccessed: Date.now() })
|
|
276
|
+
.where(eq(memoryGraphNodes.id, id))
|
|
277
|
+
.run();
|
|
278
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
279
|
+
targetType: "graph_node",
|
|
280
|
+
targetId: id,
|
|
281
|
+
});
|
|
274
282
|
}
|
|
275
283
|
|
|
276
284
|
// ---------------------------------------------------------------------------
|
|
@@ -343,7 +351,7 @@ export function queryNodes(filters: NodeQueryFilters): MemoryNode[] {
|
|
|
343
351
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
344
352
|
.orderBy(sql`${memoryGraphNodes.significance} DESC`);
|
|
345
353
|
|
|
346
|
-
if (filters.limit) {
|
|
354
|
+
if (filters.limit != null) {
|
|
347
355
|
query = query.limit(filters.limit) as typeof query;
|
|
348
356
|
}
|
|
349
357
|
|
|
@@ -396,12 +404,19 @@ export function getEdgesForNode(
|
|
|
396
404
|
direction?: "incoming" | "outgoing",
|
|
397
405
|
): MemoryEdge[] {
|
|
398
406
|
const db = getDb();
|
|
399
|
-
const
|
|
407
|
+
const dirCondition =
|
|
400
408
|
direction === "outgoing"
|
|
401
409
|
? eq(memoryGraphEdges.sourceNodeId, nodeId)
|
|
402
410
|
: direction === "incoming"
|
|
403
411
|
? eq(memoryGraphEdges.targetNodeId, nodeId)
|
|
404
|
-
:
|
|
412
|
+
: or(eq(memoryGraphEdges.sourceNodeId, nodeId), eq(memoryGraphEdges.targetNodeId, nodeId));
|
|
413
|
+
|
|
414
|
+
// Exclude edges where either endpoint has fidelity='gone' (soft-deleted)
|
|
415
|
+
const condition = and(
|
|
416
|
+
dirCondition,
|
|
417
|
+
sql`NOT EXISTS (SELECT 1 FROM ${memoryGraphNodes} WHERE ${memoryGraphNodes.id} = ${memoryGraphEdges.sourceNodeId} AND ${memoryGraphNodes.fidelity} = 'gone')`,
|
|
418
|
+
sql`NOT EXISTS (SELECT 1 FROM ${memoryGraphNodes} WHERE ${memoryGraphNodes.id} = ${memoryGraphEdges.targetNodeId} AND ${memoryGraphNodes.fidelity} = 'gone')`,
|
|
419
|
+
);
|
|
405
420
|
|
|
406
421
|
return db
|
|
407
422
|
.select()
|
|
@@ -512,12 +527,23 @@ export function getActiveTriggersByType(
|
|
|
512
527
|
return rows.map((r) => rowToTrigger(r.trigger));
|
|
513
528
|
}
|
|
514
529
|
|
|
515
|
-
|
|
516
|
-
.select(
|
|
530
|
+
const rows = db
|
|
531
|
+
.select({
|
|
532
|
+
trigger: memoryGraphTriggers,
|
|
533
|
+
})
|
|
517
534
|
.from(memoryGraphTriggers)
|
|
518
|
-
.
|
|
519
|
-
|
|
520
|
-
|
|
535
|
+
.innerJoin(
|
|
536
|
+
memoryGraphNodes,
|
|
537
|
+
eq(memoryGraphTriggers.nodeId, memoryGraphNodes.id),
|
|
538
|
+
)
|
|
539
|
+
.where(
|
|
540
|
+
and(
|
|
541
|
+
...conditions,
|
|
542
|
+
sql`${memoryGraphNodes.fidelity} != 'gone'`,
|
|
543
|
+
),
|
|
544
|
+
)
|
|
545
|
+
.all();
|
|
546
|
+
return rows.map((r) => rowToTrigger(r.trigger));
|
|
521
547
|
}
|
|
522
548
|
|
|
523
549
|
// ---------------------------------------------------------------------------
|
|
@@ -614,9 +640,18 @@ export function applyDiff(
|
|
|
614
640
|
};
|
|
615
641
|
|
|
616
642
|
db.transaction((tx) => {
|
|
617
|
-
//
|
|
643
|
+
// Soft-delete nodes (set fidelity='gone' and enqueue Qdrant cleanup)
|
|
618
644
|
for (const id of diff.deleteNodeIds) {
|
|
619
|
-
tx.
|
|
645
|
+
tx.update(memoryGraphNodes)
|
|
646
|
+
.set({ fidelity: "gone", lastAccessed: Date.now() })
|
|
647
|
+
.where(eq(memoryGraphNodes.id, id))
|
|
648
|
+
.run();
|
|
649
|
+
enqueueMemoryJob(
|
|
650
|
+
"delete_qdrant_vectors",
|
|
651
|
+
{ targetType: "graph_node", targetId: id },
|
|
652
|
+
Date.now(),
|
|
653
|
+
tx,
|
|
654
|
+
);
|
|
620
655
|
result.nodesDeleted++;
|
|
621
656
|
}
|
|
622
657
|
|
|
@@ -301,7 +301,7 @@ export function getUsageHourBuckets(range: UsageTimeRange): UsageDayBucket[] {
|
|
|
301
301
|
}));
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
type GroupByDimension = "actor" | "provider" | "model";
|
|
304
|
+
type GroupByDimension = "actor" | "provider" | "model" | "conversation";
|
|
305
305
|
|
|
306
306
|
/**
|
|
307
307
|
* Return grouped breakdowns across the given time range, ordered by total
|
|
@@ -312,10 +312,51 @@ export function getUsageGroupBreakdown(
|
|
|
312
312
|
groupBy: GroupByDimension,
|
|
313
313
|
): UsageGroupBreakdown[] {
|
|
314
314
|
// Runtime allowlist — defense-in-depth against SQL injection via type assertions.
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
315
|
+
const ALLOWED_DIMENSIONS = new Set<string>([
|
|
316
|
+
"actor",
|
|
317
|
+
"provider",
|
|
318
|
+
"model",
|
|
319
|
+
"conversation",
|
|
320
|
+
]);
|
|
321
|
+
if (!ALLOWED_DIMENSIONS.has(groupBy)) {
|
|
322
|
+
throw new Error(`Invalid groupBy dimension: ${groupBy}`);
|
|
318
323
|
}
|
|
324
|
+
|
|
325
|
+
// Conversation grouping requires a JOIN with conversations to resolve titles.
|
|
326
|
+
if (groupBy === "conversation") {
|
|
327
|
+
const rows = rawAll<GroupRow>(
|
|
328
|
+
/*sql*/ `
|
|
329
|
+
SELECT
|
|
330
|
+
CASE WHEN e.conversation_id IS NULL THEN 'Other'
|
|
331
|
+
ELSE COALESCE(c.title, 'Untitled')
|
|
332
|
+
END AS group_key,
|
|
333
|
+
COALESCE(SUM(e.input_tokens), 0) AS total_input_tokens,
|
|
334
|
+
COALESCE(SUM(e.output_tokens), 0) AS total_output_tokens,
|
|
335
|
+
COALESCE(SUM(e.cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
|
|
336
|
+
COALESCE(SUM(e.cache_read_input_tokens), 0) AS total_cache_read_tokens,
|
|
337
|
+
COALESCE(SUM(e.estimated_cost_usd), 0) AS total_estimated_cost_usd,
|
|
338
|
+
COALESCE(SUM(COALESCE(e.llm_call_count, 1)), 0) AS event_count
|
|
339
|
+
FROM llm_usage_events e
|
|
340
|
+
LEFT JOIN conversations c ON e.conversation_id = c.id
|
|
341
|
+
WHERE e.created_at >= ?1 AND e.created_at <= ?2
|
|
342
|
+
GROUP BY e.conversation_id
|
|
343
|
+
ORDER BY total_estimated_cost_usd DESC
|
|
344
|
+
LIMIT 50
|
|
345
|
+
`,
|
|
346
|
+
range.from,
|
|
347
|
+
range.to,
|
|
348
|
+
);
|
|
349
|
+
return rows.map((r) => ({
|
|
350
|
+
group: r.group_key,
|
|
351
|
+
totalInputTokens: r.total_input_tokens,
|
|
352
|
+
totalOutputTokens: r.total_output_tokens,
|
|
353
|
+
totalCacheCreationTokens: r.total_cache_creation_tokens,
|
|
354
|
+
totalCacheReadTokens: r.total_cache_read_tokens,
|
|
355
|
+
totalEstimatedCostUsd: r.total_estimated_cost_usd ?? 0,
|
|
356
|
+
eventCount: r.event_count,
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
|
|
319
360
|
const column = groupBy;
|
|
320
361
|
const rows = rawAll<GroupRow>(
|
|
321
362
|
/*sql*/ `
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateOAuthProvidersScopeSeparator(database: DrizzleDb): void {
|
|
5
|
+
const raw = getSqliteFrom(database);
|
|
6
|
+
try {
|
|
7
|
+
raw.exec(
|
|
8
|
+
`ALTER TABLE oauth_providers ADD COLUMN scope_separator TEXT NOT NULL DEFAULT ' '`,
|
|
9
|
+
);
|
|
10
|
+
} catch {
|
|
11
|
+
// Column already exists — nothing to do.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateOAuthProvidersRefreshUrl(database: DrizzleDb): void {
|
|
5
|
+
const raw = getSqliteFrom(database);
|
|
6
|
+
try {
|
|
7
|
+
raw.exec(`ALTER TABLE oauth_providers ADD COLUMN refresh_url TEXT`);
|
|
8
|
+
} catch {
|
|
9
|
+
// Column already exists — nothing to do.
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateOAuthProvidersRevoke(database: DrizzleDb): void {
|
|
5
|
+
const raw = getSqliteFrom(database);
|
|
6
|
+
const columns = ["revoke_url TEXT", "revoke_body_template TEXT"];
|
|
7
|
+
for (const col of columns) {
|
|
8
|
+
try {
|
|
9
|
+
raw.exec(`ALTER TABLE oauth_providers ADD COLUMN ${col}`);
|
|
10
|
+
} catch {
|
|
11
|
+
// Column already exists — nothing to do.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Backfill `oauth_providers.token_endpoint_auth_method` for any rows where
|
|
6
|
+
* the value is NULL or empty string, setting them to the new default
|
|
7
|
+
* "client_secret_post". This brings existing rows in line with the
|
|
8
|
+
* Drizzle schema's new `.notNull().default("client_secret_post")`
|
|
9
|
+
* constraint, which is enforced at write time via the TypeScript layer.
|
|
10
|
+
*
|
|
11
|
+
* SQLite cannot retroactively add a NOT NULL constraint to an existing
|
|
12
|
+
* column without a full table rebuild, so the underlying column remains
|
|
13
|
+
* nullable at the SQLite level. All writes go through Drizzle, which
|
|
14
|
+
* applies the default for any insert that omits the field.
|
|
15
|
+
*
|
|
16
|
+
* The UPDATE is inherently idempotent and safe to re-run. Errors are
|
|
17
|
+
* allowed to propagate to the migration runner in `db-init.ts`, which
|
|
18
|
+
* records the failure, logs it, and continues to the next migration.
|
|
19
|
+
*/
|
|
20
|
+
export function migrateOAuthProvidersTokenAuthMethodDefault(
|
|
21
|
+
database: DrizzleDb,
|
|
22
|
+
): void {
|
|
23
|
+
const raw = getSqliteFrom(database);
|
|
24
|
+
raw.exec(
|
|
25
|
+
`UPDATE oauth_providers
|
|
26
|
+
SET token_endpoint_auth_method = 'client_secret_post'
|
|
27
|
+
WHERE token_endpoint_auth_method IS NULL
|
|
28
|
+
OR token_endpoint_auth_method = ''`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
import { withCrashRecovery } from "./validate-migration-state.js";
|
|
4
|
+
|
|
5
|
+
const CHECKPOINT_KEY = "migration_conversation_host_access_v1";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Add conversation-scoped host access state with a safe default of disabled.
|
|
9
|
+
*
|
|
10
|
+
* Idempotent: ALTER TABLE is guarded and the backfill only touches NULL rows.
|
|
11
|
+
*/
|
|
12
|
+
export function migrateConversationHostAccess(database: DrizzleDb): void {
|
|
13
|
+
withCrashRecovery(database, CHECKPOINT_KEY, () => {
|
|
14
|
+
const raw = getSqliteFrom(database);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
raw.exec(
|
|
18
|
+
`ALTER TABLE conversations ADD COLUMN host_access INTEGER NOT NULL DEFAULT 0`,
|
|
19
|
+
);
|
|
20
|
+
} catch {
|
|
21
|
+
// Column already exists.
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
raw.exec(`
|
|
25
|
+
UPDATE conversations
|
|
26
|
+
SET host_access = 0
|
|
27
|
+
WHERE host_access IS NULL
|
|
28
|
+
`);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reverse: no-op.
|
|
34
|
+
*
|
|
35
|
+
* The forward migration is additive and SQLite cannot drop one column without
|
|
36
|
+
* rebuilding the table.
|
|
37
|
+
*/
|
|
38
|
+
export function downConversationHostAccess(_database: DrizzleDb): void {
|
|
39
|
+
// Intentionally empty.
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateOAuthProvidersLogoUrl(database: DrizzleDb): void {
|
|
5
|
+
const raw = getSqliteFrom(database);
|
|
6
|
+
try {
|
|
7
|
+
raw.exec(`ALTER TABLE oauth_providers ADD COLUMN logo_url TEXT`);
|
|
8
|
+
} catch {
|
|
9
|
+
// Column already exists — nothing to do.
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -154,6 +154,12 @@ export { migrateStripThinkingFromConsolidated } from "./209-strip-thinking-from-
|
|
|
154
154
|
export { migrateScheduleReuseConversation } from "./210-schedule-reuse-conversation.js";
|
|
155
155
|
export { migrateMemoryRecallLogsQueryContext } from "./211-memory-recall-logs-query-context.js";
|
|
156
156
|
export { migrateLlmRequestLogsCreatedAtIndex } from "./212-llm-request-logs-created-at-index.js";
|
|
157
|
+
export { migrateOAuthProvidersScopeSeparator } from "./213-oauth-providers-scope-separator.js";
|
|
158
|
+
export { migrateOAuthProvidersRefreshUrl } from "./214-oauth-providers-refresh-url.js";
|
|
159
|
+
export { migrateOAuthProvidersRevoke } from "./215-oauth-providers-revoke.js";
|
|
160
|
+
export { migrateOAuthProvidersTokenAuthMethodDefault } from "./216-oauth-providers-token-auth-method.js";
|
|
161
|
+
export { migrateConversationHostAccess } from "./217-conversation-host-access.js";
|
|
162
|
+
export { migrateOAuthProvidersLogoUrl } from "./218-oauth-providers-logo-url.js";
|
|
157
163
|
export {
|
|
158
164
|
MIGRATION_REGISTRY,
|
|
159
165
|
type MigrationRegistryEntry,
|
|
@@ -41,6 +41,7 @@ import { migrateAddSourceTypeColumnsDown } from "./193-add-source-type-columns.j
|
|
|
41
41
|
import { migrateStripIntegrationPrefixFromProviderKeysDown } from "./196-strip-integration-prefix-from-provider-keys.js";
|
|
42
42
|
import { migrateRenameMemoryGraphTypeValuesDown } from "./204-rename-memory-graph-type-values.js";
|
|
43
43
|
import { migrateScrubCorruptedImageAttachmentsDown } from "./206-scrub-corrupted-image-attachments.js";
|
|
44
|
+
import { downConversationHostAccess } from "./217-conversation-host-access.js";
|
|
44
45
|
|
|
45
46
|
export interface MigrationRegistryEntry {
|
|
46
47
|
/** The checkpoint key written to memory_checkpoints on completion. */
|
|
@@ -357,6 +358,13 @@ export const MIGRATION_REGISTRY: MigrationRegistryEntry[] = [
|
|
|
357
358
|
"Remove image attachments containing HTML error pages instead of image data",
|
|
358
359
|
down: migrateScrubCorruptedImageAttachmentsDown,
|
|
359
360
|
},
|
|
361
|
+
{
|
|
362
|
+
key: "migration_conversation_host_access_v1",
|
|
363
|
+
version: 41,
|
|
364
|
+
description:
|
|
365
|
+
"Add a host_access column to conversations so computer access is persisted per conversation with a safe default of disabled",
|
|
366
|
+
down: downConversationHostAccess,
|
|
367
|
+
},
|
|
360
368
|
];
|
|
361
369
|
|
|
362
370
|
export function getMaxMigrationVersion(): number {
|
|
@@ -28,6 +28,7 @@ export const conversations = sqliteTable(
|
|
|
28
28
|
originInterface: text("origin_interface"),
|
|
29
29
|
forkParentConversationId: text("fork_parent_conversation_id"),
|
|
30
30
|
forkParentMessageId: text("fork_parent_message_id"),
|
|
31
|
+
hostAccess: integer("host_access").notNull().default(0),
|
|
31
32
|
isAutoTitle: integer("is_auto_title").notNull().default(1),
|
|
32
33
|
scheduleJobId: text("schedule_job_id"),
|
|
33
34
|
lastMessageAt: integer("last_message_at"),
|
|
@@ -7,24 +7,31 @@ import {
|
|
|
7
7
|
} from "drizzle-orm/sqlite-core";
|
|
8
8
|
|
|
9
9
|
export const oauthProviders = sqliteTable("oauth_providers", {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
provider: text("provider_key").primaryKey(),
|
|
11
|
+
authorizeUrl: text("auth_url").notNull(),
|
|
12
|
+
tokenExchangeUrl: text("token_url").notNull(),
|
|
13
|
+
refreshUrl: text("refresh_url"),
|
|
14
|
+
tokenEndpointAuthMethod: text("token_endpoint_auth_method")
|
|
15
|
+
.notNull()
|
|
16
|
+
.default("client_secret_post"),
|
|
14
17
|
userinfoUrl: text("userinfo_url"),
|
|
15
18
|
baseUrl: text("base_url"),
|
|
16
19
|
defaultScopes: text("default_scopes").notNull().default("[]"),
|
|
17
20
|
scopePolicy: text("scope_policy").notNull().default("{}"),
|
|
18
|
-
|
|
21
|
+
scopeSeparator: text("scope_separator").notNull().default(" "),
|
|
22
|
+
authorizeParams: text("extra_params"),
|
|
19
23
|
pingUrl: text("ping_url"),
|
|
20
24
|
pingMethod: text("ping_method"),
|
|
21
25
|
pingHeaders: text("ping_headers"),
|
|
22
26
|
pingBody: text("ping_body"),
|
|
27
|
+
revokeUrl: text("revoke_url"),
|
|
28
|
+
revokeBodyTemplate: text("revoke_body_template"),
|
|
23
29
|
managedServiceConfigKey: text("managed_service_config_key"),
|
|
24
|
-
|
|
30
|
+
displayLabel: text("display_name"),
|
|
25
31
|
description: text("description"),
|
|
26
32
|
dashboardUrl: text("dashboard_url"),
|
|
27
33
|
clientIdPlaceholder: text("client_id_placeholder"),
|
|
34
|
+
logoUrl: text("logo_url"),
|
|
28
35
|
requiresClientSecret: integer("requires_client_secret").notNull().default(1),
|
|
29
36
|
loopbackPort: integer("loopback_port"),
|
|
30
37
|
injectionTemplates: text("injection_templates"),
|
|
@@ -46,9 +53,9 @@ export const oauthApps = sqliteTable(
|
|
|
46
53
|
"oauth_apps",
|
|
47
54
|
{
|
|
48
55
|
id: text("id").primaryKey(),
|
|
49
|
-
|
|
56
|
+
provider: text("provider_key")
|
|
50
57
|
.notNull()
|
|
51
|
-
.references(() => oauthProviders.
|
|
58
|
+
.references(() => oauthProviders.provider),
|
|
52
59
|
clientId: text("client_id").notNull(),
|
|
53
60
|
clientSecretCredentialPath: text("client_secret_credential_path").notNull(),
|
|
54
61
|
createdAt: integer("created_at").notNull(),
|
|
@@ -56,7 +63,7 @@ export const oauthApps = sqliteTable(
|
|
|
56
63
|
},
|
|
57
64
|
(table) => [
|
|
58
65
|
uniqueIndex("idx_oauth_apps_provider_client").on(
|
|
59
|
-
table.
|
|
66
|
+
table.provider,
|
|
60
67
|
table.clientId,
|
|
61
68
|
),
|
|
62
69
|
],
|
|
@@ -69,7 +76,7 @@ export const oauthConnections = sqliteTable(
|
|
|
69
76
|
oauthAppId: text("oauth_app_id")
|
|
70
77
|
.notNull()
|
|
71
78
|
.references(() => oauthApps.id),
|
|
72
|
-
|
|
79
|
+
provider: text("provider_key").notNull(),
|
|
73
80
|
accountInfo: text("account_info"),
|
|
74
81
|
grantedScopes: text("granted_scopes").notNull().default("[]"),
|
|
75
82
|
expiresAt: integer("expires_at"),
|
|
@@ -80,7 +87,5 @@ export const oauthConnections = sqliteTable(
|
|
|
80
87
|
createdAt: integer("created_at").notNull(),
|
|
81
88
|
updatedAt: integer("updated_at").notNull(),
|
|
82
89
|
},
|
|
83
|
-
(table) => [
|
|
84
|
-
index("idx_oauth_connections_provider_key").on(table.providerKey),
|
|
85
|
-
],
|
|
90
|
+
(table) => [index("idx_oauth_connections_provider_key").on(table.provider)],
|
|
86
91
|
);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# OAuth — Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Adding a New First-Class Provider
|
|
4
|
+
|
|
5
|
+
When introducing a new built-in OAuth integration (one that appears in `seed-providers.ts`), touch each of the following areas. Items marked _(managed only)_ apply only when the provider supports platform-provided credentials.
|
|
6
|
+
|
|
7
|
+
### 1. Seed the provider — `seed-providers.ts`
|
|
8
|
+
|
|
9
|
+
Add an entry to `PROVIDER_SEED_DATA`. Required fields: `provider`, `authorizeUrl`, `tokenExchangeUrl`, `defaultScopes`, `scopePolicy`, `displayLabel`, `description`, `dashboardUrl`, `clientIdPlaceholder`, `logoUrl`, and `injectionTemplates`. See existing entries for the full shape. The `provider` key must be snake_case and is used as the canonical identifier everywhere else.
|
|
10
|
+
|
|
11
|
+
If the provider will support managed mode, set `managedServiceConfigKey` to a slug matching the key you will add to `ServicesSchema` (e.g. `"acme-oauth"`).
|
|
12
|
+
|
|
13
|
+
### 2. _(managed only)_ Add a service schema — `../config/schemas/services.ts`
|
|
14
|
+
|
|
15
|
+
Create a schema and export its type:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
export const AcmeOAuthServiceSchema = BaseServiceSchema.extend({
|
|
19
|
+
mode: ServiceModeSchema.default("your-own"),
|
|
20
|
+
});
|
|
21
|
+
export type AcmeOAuthService = z.infer<typeof AcmeOAuthServiceSchema>;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Then add the key to `ServicesSchema`:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
"acme-oauth": AcmeOAuthServiceSchema.default(AcmeOAuthServiceSchema.parse({})),
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The key here **must** match the `managedServiceConfigKey` in `seed-providers.ts`. The cross-repo invariant test in `__tests__/seed-providers-managed.test.ts` will fail if they drift.
|
|
31
|
+
|
|
32
|
+
### 3. _(managed only)_ Enable by default during onboarding — `clients/macos/.../HatchingStepView.swift`
|
|
33
|
+
|
|
34
|
+
In `buildOnboardingConfigValues()`, add a line so managed-sign-in users get the integration pre-enabled:
|
|
35
|
+
|
|
36
|
+
```swift
|
|
37
|
+
configValues["services.acme-oauth.mode"] = "managed"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 4. Add a cached logo — `clients/shared/Resources/IntegrationLogos/`
|
|
41
|
+
|
|
42
|
+
Drop a **vector PDF** named `{provider_key}.pdf` (e.g. `acme.pdf`) into the `IntegrationLogos/` directory. The file is automatically bundled via `.copy()` in `clients/Package.swift` and looked up at runtime by `IntegrationLogoBundle` using the provider key — no code changes needed.
|
|
43
|
+
|
|
44
|
+
Most existing logos come from [Simple Icons](https://simpleicons.org) (CC0-licensed). To get a PDF from Simple Icons:
|
|
45
|
+
|
|
46
|
+
1. Find the icon slug on https://simpleicons.org (e.g. `slack`, `linear`).
|
|
47
|
+
2. Download the SVG: `curl -o acme.svg https://raw.githubusercontent.com/simple-icons/simple-icons/develop/icons/acme.svg`
|
|
48
|
+
3. Convert to PDF using `rsvg-convert` (same tool used by `clients/scripts/sync-lucide-icons.sh`):
|
|
49
|
+
```bash
|
|
50
|
+
# Install if needed: brew install librsvg
|
|
51
|
+
rsvg-convert -f pdf -o clients/shared/Resources/IntegrationLogos/acme.pdf acme.svg
|
|
52
|
+
```
|
|
53
|
+
4. Add the provider key to `clients/shared/Resources/integration-logos-manifest.json`.
|
|
54
|
+
|
|
55
|
+
If the service is not on Simple Icons, source or create an SVG and convert it the same way. The result must be a true vector PDF (not a rasterized image wrapped in PDF) so it scales cleanly.
|
|
56
|
+
|
|
57
|
+
The `logoUrl` field in `seed-providers.ts` still serves as the remote fallback (typically a Simple Icons CDN URL like `https://cdn.simpleicons.org/acme`). The client renders the local PDF first, then falls back to `logoUrl`, then to an initials avatar.
|
|
58
|
+
|
|
59
|
+
### 5. Secret patterns (if applicable) — `../security/secret-patterns.ts`
|
|
60
|
+
|
|
61
|
+
If the provider issues API keys with a recognizable prefix (e.g. `acme_sk_`), add a `PREFIX_PATTERNS` entry. OAuth-only services with opaque access tokens do not need one. See `../security/AGENTS.md` for details.
|
|
62
|
+
|
|
63
|
+
### 6. Feature-flag gating (optional) — `seed-providers.ts`
|
|
64
|
+
|
|
65
|
+
Set `featureFlag: "acme-oauth"` in the seed entry and register the flag in `meta/feature-flags/feature-flag-registry.json` to hide the provider until the flag is enabled. Omit `featureFlag` to make the provider visible immediately.
|
|
66
|
+
|
|
67
|
+
### What you do NOT need to change
|
|
68
|
+
|
|
69
|
+
The following are wired automatically once `PROVIDER_SEED_DATA` has an entry:
|
|
70
|
+
|
|
71
|
+
- **Connection resolver** (`connection-resolver.ts`) — routes managed vs. BYO based on config.
|
|
72
|
+
- **CLI commands** (`../cli/commands/oauth/`) — `providers list`, `providers get`, `connect`, `disconnect`, etc.
|
|
73
|
+
- **Runtime API** (`../runtime/routes/oauth-providers.ts`) — `GET /v1/oauth/providers` and related endpoints.
|
|
74
|
+
- **Gateway proxy** (`gateway/src/http/routes/oauth-providers-proxy.ts`) — forwards to the runtime.
|
|
75
|
+
- **OAuth store** (`oauth-store.ts`) — seeding uses upsert; schema already supports arbitrary providers.
|
|
76
|
+
- **Provider serialization** (`provider-serializer.ts`) — generic over all providers.
|
|
@@ -24,29 +24,34 @@ afterEach(() => {
|
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
25
|
|
|
26
26
|
function makeProviderRow(
|
|
27
|
-
overrides: Partial<OAuthProviderRow> & {
|
|
27
|
+
overrides: Partial<OAuthProviderRow> & { provider: string },
|
|
28
28
|
): OAuthProviderRow {
|
|
29
29
|
const now = Date.now();
|
|
30
|
-
const {
|
|
30
|
+
const { provider, ...rest } = overrides;
|
|
31
31
|
return {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
provider,
|
|
33
|
+
authorizeUrl: "https://example.com/auth",
|
|
34
|
+
tokenExchangeUrl: "https://example.com/token",
|
|
35
|
+
refreshUrl: null,
|
|
36
|
+
tokenEndpointAuthMethod: "client_secret_post",
|
|
36
37
|
userinfoUrl: null,
|
|
37
38
|
baseUrl: null,
|
|
38
39
|
defaultScopes: "[]",
|
|
39
40
|
scopePolicy: "{}",
|
|
40
|
-
|
|
41
|
+
scopeSeparator: " ",
|
|
42
|
+
authorizeParams: null,
|
|
41
43
|
pingUrl: null,
|
|
42
44
|
pingMethod: null,
|
|
43
45
|
pingHeaders: null,
|
|
44
46
|
pingBody: null,
|
|
47
|
+
revokeUrl: null,
|
|
48
|
+
revokeBodyTemplate: null,
|
|
45
49
|
managedServiceConfigKey: null,
|
|
46
|
-
|
|
50
|
+
displayLabel: null,
|
|
47
51
|
description: null,
|
|
48
52
|
dashboardUrl: null,
|
|
49
53
|
clientIdPlaceholder: null,
|
|
54
|
+
logoUrl: null,
|
|
50
55
|
requiresClientSecret: 1,
|
|
51
56
|
loopbackPort: null,
|
|
52
57
|
injectionTemplates: null,
|
|
@@ -82,7 +87,7 @@ describe("verifyIdentity", () => {
|
|
|
82
87
|
// Missing identity URL
|
|
83
88
|
// -----------------------------------------------------------------------
|
|
84
89
|
test("returns undefined when identityUrl is null", async () => {
|
|
85
|
-
const row = makeProviderRow({
|
|
90
|
+
const row = makeProviderRow({ provider: "custom" });
|
|
86
91
|
const result = await verifyIdentity(row, "token-abc");
|
|
87
92
|
expect(result).toBeUndefined();
|
|
88
93
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
@@ -93,7 +98,7 @@ describe("verifyIdentity", () => {
|
|
|
93
98
|
// -----------------------------------------------------------------------
|
|
94
99
|
describe("Google pattern", () => {
|
|
95
100
|
const googleRow = makeProviderRow({
|
|
96
|
-
|
|
101
|
+
provider: "google",
|
|
97
102
|
identityUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
98
103
|
identityResponsePaths: JSON.stringify(["email"]),
|
|
99
104
|
});
|
|
@@ -127,7 +132,7 @@ describe("verifyIdentity", () => {
|
|
|
127
132
|
// -----------------------------------------------------------------------
|
|
128
133
|
describe("Slack pattern", () => {
|
|
129
134
|
const slackRow = makeProviderRow({
|
|
130
|
-
|
|
135
|
+
provider: "slack",
|
|
131
136
|
identityUrl: "https://slack.com/api/auth.test",
|
|
132
137
|
identityOkField: "ok",
|
|
133
138
|
identityResponsePaths: JSON.stringify(["user", "team"]),
|
|
@@ -176,7 +181,7 @@ describe("verifyIdentity", () => {
|
|
|
176
181
|
// -----------------------------------------------------------------------
|
|
177
182
|
describe("HubSpot pattern", () => {
|
|
178
183
|
const hubspotRow = makeProviderRow({
|
|
179
|
-
|
|
184
|
+
provider: "hubspot",
|
|
180
185
|
identityUrl:
|
|
181
186
|
"https://api.hubapi.com/oauth/v1/access-tokens/${accessToken}",
|
|
182
187
|
identityResponsePaths: JSON.stringify(["user", "hub_domain"]),
|
|
@@ -219,7 +224,7 @@ describe("verifyIdentity", () => {
|
|
|
219
224
|
// -----------------------------------------------------------------------
|
|
220
225
|
describe("Linear pattern", () => {
|
|
221
226
|
const linearRow = makeProviderRow({
|
|
222
|
-
|
|
227
|
+
provider: "linear",
|
|
223
228
|
identityUrl: "https://api.linear.app/graphql",
|
|
224
229
|
identityMethod: "POST",
|
|
225
230
|
identityHeaders: JSON.stringify({ "Content-Type": "application/json" }),
|
|
@@ -272,7 +277,7 @@ describe("verifyIdentity", () => {
|
|
|
272
277
|
// -----------------------------------------------------------------------
|
|
273
278
|
describe("Todoist pattern", () => {
|
|
274
279
|
const todoistRow = makeProviderRow({
|
|
275
|
-
|
|
280
|
+
provider: "todoist",
|
|
276
281
|
identityUrl: "https://api.todoist.com/sync/v9/sync",
|
|
277
282
|
identityMethod: "POST",
|
|
278
283
|
identityHeaders: JSON.stringify({
|
|
@@ -317,7 +322,7 @@ describe("verifyIdentity", () => {
|
|
|
317
322
|
// -----------------------------------------------------------------------
|
|
318
323
|
describe("Twitter pattern", () => {
|
|
319
324
|
const twitterRow = makeProviderRow({
|
|
320
|
-
|
|
325
|
+
provider: "twitter",
|
|
321
326
|
identityUrl: "https://api.x.com/2/users/me",
|
|
322
327
|
identityResponsePaths: JSON.stringify(["data.username"]),
|
|
323
328
|
identityFormat: "@${data.username}",
|
|
@@ -338,7 +343,7 @@ describe("verifyIdentity", () => {
|
|
|
338
343
|
// -----------------------------------------------------------------------
|
|
339
344
|
describe("GitHub pattern", () => {
|
|
340
345
|
const githubRow = makeProviderRow({
|
|
341
|
-
|
|
346
|
+
provider: "github",
|
|
342
347
|
identityUrl: "https://api.github.com/user",
|
|
343
348
|
identityResponsePaths: JSON.stringify(["login"]),
|
|
344
349
|
identityFormat: "@${login}",
|
|
@@ -357,7 +362,7 @@ describe("verifyIdentity", () => {
|
|
|
357
362
|
// -----------------------------------------------------------------------
|
|
358
363
|
describe("Notion pattern", () => {
|
|
359
364
|
const notionRow = makeProviderRow({
|
|
360
|
-
|
|
365
|
+
provider: "notion",
|
|
361
366
|
identityUrl: "https://api.notion.com/v1/users/me",
|
|
362
367
|
identityHeaders: JSON.stringify({ "Notion-Version": "2022-06-28" }),
|
|
363
368
|
identityResponsePaths: JSON.stringify(["name", "person.email"]),
|
|
@@ -392,7 +397,7 @@ describe("verifyIdentity", () => {
|
|
|
392
397
|
// -----------------------------------------------------------------------
|
|
393
398
|
describe("error handling", () => {
|
|
394
399
|
const googleRow = makeProviderRow({
|
|
395
|
-
|
|
400
|
+
provider: "google",
|
|
396
401
|
identityUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
397
402
|
identityResponsePaths: JSON.stringify(["email"]),
|
|
398
403
|
});
|
|
@@ -431,7 +436,7 @@ describe("verifyIdentity", () => {
|
|
|
431
436
|
// -----------------------------------------------------------------------
|
|
432
437
|
describe("Dropbox pattern", () => {
|
|
433
438
|
const dropboxRow = makeProviderRow({
|
|
434
|
-
|
|
439
|
+
provider: "dropbox",
|
|
435
440
|
identityUrl: "https://api.dropboxapi.com/2/users/get_current_account",
|
|
436
441
|
identityMethod: "POST",
|
|
437
442
|
identityResponsePaths: JSON.stringify(["name.display_name", "email"]),
|