@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
|
@@ -36,12 +36,13 @@ import {
|
|
|
36
36
|
} from "../../daemon/first-greeting.js";
|
|
37
37
|
import { renderHistoryContent } from "../../daemon/handlers/shared.js";
|
|
38
38
|
import { HostBashProxy } from "../../daemon/host-bash-proxy.js";
|
|
39
|
+
import { HostBrowserProxy } from "../../daemon/host-browser-proxy.js";
|
|
39
40
|
import { HostCuProxy } from "../../daemon/host-cu-proxy.js";
|
|
40
41
|
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
|
|
41
42
|
import type { ServerMessage } from "../../daemon/message-protocol.js";
|
|
42
43
|
import type {
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
HostProxyTransportMetadata,
|
|
45
|
+
NonHostProxyTransportMetadata,
|
|
45
46
|
} from "../../daemon/message-types/conversations.js";
|
|
46
47
|
import type { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
|
|
47
48
|
import * as attachmentsStore from "../../memory/attachments-store.js";
|
|
@@ -77,6 +78,7 @@ import { silentlyWithLog } from "../../util/silently.js";
|
|
|
77
78
|
import { buildAssistantEvent } from "../assistant-event.js";
|
|
78
79
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
79
80
|
import type { AuthContext } from "../auth/types.js";
|
|
81
|
+
import { getChromeExtensionRegistry } from "../chrome-extension-registry.js";
|
|
80
82
|
import { bridgeConfirmationRequestToGuardian } from "../confirmation-request-guardian-bridge.js";
|
|
81
83
|
import { routeGuardianReply } from "../guardian-reply-router.js";
|
|
82
84
|
import { healGuardianBindingDrift } from "../guardian-vellum-migration.js";
|
|
@@ -422,8 +424,18 @@ export function handleListMessages(
|
|
|
422
424
|
// pendingToolUses map — otherwise they render as "Unknown" tool calls.
|
|
423
425
|
const mergedMessages = mergeToolResultsIntoAssistantMessages(rawMessages);
|
|
424
426
|
|
|
427
|
+
// During streaming, all assistant turns within one agent loop accumulate
|
|
428
|
+
// on a single client-side ChatMessage (via currentAssistantMessageId).
|
|
429
|
+
// In the DB, each API turn is a separate assistant row because
|
|
430
|
+
// consolidation is deferred to compaction for prefix-cache stability.
|
|
431
|
+
// Merge consecutive assistant messages here at query time so
|
|
432
|
+
// renderHistoryContent produces the same contentOrder shape as streaming
|
|
433
|
+
// (consecutive tool refs grouped together).
|
|
434
|
+
const { messages: consolidatedMessages, mergedIdMap } =
|
|
435
|
+
mergeConsecutiveAssistantMessages(mergedMessages);
|
|
436
|
+
|
|
425
437
|
// Parse content blocks and extract text + tool calls
|
|
426
|
-
const parsed =
|
|
438
|
+
const parsed = consolidatedMessages.map((msg) => {
|
|
427
439
|
let content: unknown;
|
|
428
440
|
try {
|
|
429
441
|
content = JSON.parse(msg.content);
|
|
@@ -548,7 +560,13 @@ export function handleListMessages(
|
|
|
548
560
|
// blobs for non-image attachments (documents, audio). Then
|
|
549
561
|
// selectively fetch full data only for images so the client can
|
|
550
562
|
// generate thumbnails for inline display on history restore.
|
|
551
|
-
|
|
563
|
+
// Also query attachments for any messages that were merged into
|
|
564
|
+
// this one (consecutive assistant merge), so their attachments
|
|
565
|
+
// aren't lost before DB compaction relinks them.
|
|
566
|
+
const idsToQuery = [m.id, ...(mergedIdMap.get(m.id) ?? [])];
|
|
567
|
+
const linked = idsToQuery.flatMap((id) =>
|
|
568
|
+
attachmentsStore.getAttachmentMetadataForMessage(id),
|
|
569
|
+
);
|
|
552
570
|
if (linked.length > 0) {
|
|
553
571
|
msgAttachments = linked.map((a) => {
|
|
554
572
|
if (a.mimeType.startsWith("image/")) {
|
|
@@ -793,14 +811,158 @@ function mergeToolResultsIntoAssistantMessages(
|
|
|
793
811
|
return result;
|
|
794
812
|
}
|
|
795
813
|
|
|
814
|
+
// ── Consecutive assistant message merging ────────────────────────────
|
|
815
|
+
|
|
816
|
+
/** Parse a message's JSON content into an array of content blocks. */
|
|
817
|
+
function parseContentBlocks(content: string): unknown[] {
|
|
818
|
+
try {
|
|
819
|
+
const parsed = JSON.parse(content);
|
|
820
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
821
|
+
} catch (err) {
|
|
822
|
+
log.warn(
|
|
823
|
+
{ err },
|
|
824
|
+
"Failed to parse content blocks during assistant message merge",
|
|
825
|
+
);
|
|
826
|
+
return [];
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Append content blocks from a donor message onto a target block array.
|
|
832
|
+
* Parses the donor's JSON content and pushes each block into `target`.
|
|
833
|
+
*/
|
|
834
|
+
function appendContentBlocks(target: unknown[], donorContent: string): void {
|
|
835
|
+
try {
|
|
836
|
+
const parsed = JSON.parse(donorContent);
|
|
837
|
+
if (Array.isArray(parsed)) {
|
|
838
|
+
target.push(...parsed);
|
|
839
|
+
} else {
|
|
840
|
+
target.push(parsed);
|
|
841
|
+
}
|
|
842
|
+
} catch (err) {
|
|
843
|
+
log.warn(
|
|
844
|
+
{ err },
|
|
845
|
+
"Failed to parse donor content blocks during assistant message merge",
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Promote metadata fields from a donor message to the surviving message
|
|
852
|
+
* when the survivor lacks them. Currently promotes `subagentNotification`.
|
|
853
|
+
* Returns a new MessageRow if promotion occurred, otherwise the original.
|
|
854
|
+
*/
|
|
855
|
+
function promoteMetadata(survivor: MessageRow, donor: MessageRow): MessageRow {
|
|
856
|
+
if (donor.metadata && survivor.metadata) {
|
|
857
|
+
try {
|
|
858
|
+
const survivorMeta = JSON.parse(survivor.metadata);
|
|
859
|
+
const donorMeta = JSON.parse(donor.metadata);
|
|
860
|
+
if (
|
|
861
|
+
!survivorMeta.subagentNotification &&
|
|
862
|
+
donorMeta.subagentNotification
|
|
863
|
+
) {
|
|
864
|
+
survivorMeta.subagentNotification = donorMeta.subagentNotification;
|
|
865
|
+
return { ...survivor, metadata: JSON.stringify(survivorMeta) };
|
|
866
|
+
}
|
|
867
|
+
} catch (err) {
|
|
868
|
+
log.warn(
|
|
869
|
+
{ err },
|
|
870
|
+
"Failed to parse metadata during assistant message merge",
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
} else if (donor.metadata && !survivor.metadata) {
|
|
874
|
+
return { ...survivor, metadata: donor.metadata };
|
|
875
|
+
}
|
|
876
|
+
return survivor;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Merge consecutive assistant messages into a single message at query time.
|
|
881
|
+
*
|
|
882
|
+
* During streaming, all assistant turns within one agent loop accumulate on
|
|
883
|
+
* a single client-side ChatMessage. In the DB, each API turn is stored as a
|
|
884
|
+
* separate assistant row (consolidation is deferred to compaction for
|
|
885
|
+
* prefix-cache stability). This produces N separate assistant messages that
|
|
886
|
+
* the client renders as N individual bubbles — each showing "Completed 1
|
|
887
|
+
* step" instead of one grouped "Completed N steps" accordion.
|
|
888
|
+
*
|
|
889
|
+
* This function concatenates the content block arrays of consecutive
|
|
890
|
+
* assistant messages (no intervening user messages after tool-result
|
|
891
|
+
* merging) into the first message of each run. The merged messages are
|
|
892
|
+
* removed from the output. This is query-time only — the DB is not
|
|
893
|
+
* modified.
|
|
894
|
+
*
|
|
895
|
+
* The first message in each run keeps its id, createdAt, and metadata so
|
|
896
|
+
* that attachment lookups, display timestamps, and subagent notifications
|
|
897
|
+
* continue to work. Metadata from later messages in the run (e.g.
|
|
898
|
+
* subagentNotification) is preserved by promoting it to the surviving
|
|
899
|
+
* message when the surviving message has no metadata of its own for that
|
|
900
|
+
* field.
|
|
901
|
+
*/
|
|
902
|
+
function mergeConsecutiveAssistantMessages(messages: MessageRow[]): {
|
|
903
|
+
messages: MessageRow[];
|
|
904
|
+
/** Maps each surviving message ID → all original message IDs merged into it. */
|
|
905
|
+
mergedIdMap: Map<string, string[]>;
|
|
906
|
+
} {
|
|
907
|
+
const result: MessageRow[] = [];
|
|
908
|
+
// Key = index in `result`, value = accumulated content blocks.
|
|
909
|
+
const pendingMerges = new Map<number, unknown[]>();
|
|
910
|
+
// Key = index in `result`, value = IDs of messages merged into the target.
|
|
911
|
+
const mergedIds = new Map<number, string[]>();
|
|
912
|
+
|
|
913
|
+
for (const msg of messages) {
|
|
914
|
+
const lastIdx = result.length - 1;
|
|
915
|
+
const isConsecutiveAssistant =
|
|
916
|
+
msg.role === "assistant" &&
|
|
917
|
+
lastIdx >= 0 &&
|
|
918
|
+
result[lastIdx].role === "assistant";
|
|
919
|
+
|
|
920
|
+
if (!isConsecutiveAssistant) {
|
|
921
|
+
result.push(msg);
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Track the donor message ID.
|
|
926
|
+
let ids = mergedIds.get(lastIdx);
|
|
927
|
+
if (!ids) {
|
|
928
|
+
ids = [];
|
|
929
|
+
mergedIds.set(lastIdx, ids);
|
|
930
|
+
}
|
|
931
|
+
ids.push(msg.id);
|
|
932
|
+
|
|
933
|
+
// Lazily parse the target's content on first merge.
|
|
934
|
+
let targetContent = pendingMerges.get(lastIdx);
|
|
935
|
+
if (!targetContent) {
|
|
936
|
+
targetContent = parseContentBlocks(result[lastIdx].content);
|
|
937
|
+
pendingMerges.set(lastIdx, targetContent);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
appendContentBlocks(targetContent, msg.content);
|
|
941
|
+
result[lastIdx] = promoteMetadata(result[lastIdx], msg);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Write back merged content for any messages that were targets.
|
|
945
|
+
for (const [idx, content] of pendingMerges) {
|
|
946
|
+
result[idx] = { ...result[idx], content: JSON.stringify(content) };
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Build the merged ID map keyed by surviving message ID.
|
|
950
|
+
const mergedIdMap = new Map<string, string[]>();
|
|
951
|
+
for (const [idx, ids] of mergedIds) {
|
|
952
|
+
mergedIdMap.set(result[idx].id, ids);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
return { messages: result, mergedIdMap };
|
|
956
|
+
}
|
|
957
|
+
|
|
796
958
|
/**
|
|
797
959
|
* Build an `onEvent` callback that publishes every outbound event to the
|
|
798
960
|
* assistant event hub, maintaining ordered delivery through a serial chain.
|
|
799
961
|
*
|
|
800
962
|
* Also registers pending interactions when confirmation_request,
|
|
801
|
-
* secret_request, host_bash_request,
|
|
802
|
-
* through, so standalone approval/result
|
|
803
|
-
* by requestId.
|
|
963
|
+
* secret_request, host_bash_request, host_browser_request, host_file_request,
|
|
964
|
+
* or host_cu_request events flow through, so standalone approval/result
|
|
965
|
+
* endpoints can look up the conversation by requestId.
|
|
804
966
|
*/
|
|
805
967
|
function makeHubPublisher(
|
|
806
968
|
deps: SendMessageDeps,
|
|
@@ -892,6 +1054,12 @@ function makeHubPublisher(
|
|
|
892
1054
|
conversationId,
|
|
893
1055
|
kind: "host_bash",
|
|
894
1056
|
});
|
|
1057
|
+
} else if (msg.type === "host_browser_request") {
|
|
1058
|
+
pendingInteractions.register(msg.requestId, {
|
|
1059
|
+
conversation,
|
|
1060
|
+
conversationId,
|
|
1061
|
+
kind: "host_browser",
|
|
1062
|
+
});
|
|
895
1063
|
} else if (msg.type === "host_file_request") {
|
|
896
1064
|
pendingInteractions.register(msg.requestId, {
|
|
897
1065
|
conversation,
|
|
@@ -1058,18 +1226,21 @@ export async function handleSendMessage(
|
|
|
1058
1226
|
|
|
1059
1227
|
// Build transport metadata from the request so the daemon can inject
|
|
1060
1228
|
// host environment hints (home directory, username) into the LLM context.
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1229
|
+
// The `supportsHostProxy` type predicate narrows `sourceInterface` to
|
|
1230
|
+
// `HostProxyInterfaceId` in the truthy branch, which is exactly the
|
|
1231
|
+
// discriminant the `HostProxyTransportMetadata` variant expects — so the
|
|
1232
|
+
// construction site stays in lock-step with the runtime capability gate.
|
|
1233
|
+
const transport = supportsHostProxy(sourceInterface)
|
|
1234
|
+
? ({
|
|
1235
|
+
channelId: sourceChannel,
|
|
1236
|
+
interfaceId: sourceInterface,
|
|
1237
|
+
hostHomeDir: body.hostHomeDir,
|
|
1238
|
+
hostUsername: body.hostUsername,
|
|
1239
|
+
} satisfies HostProxyTransportMetadata)
|
|
1240
|
+
: ({
|
|
1241
|
+
channelId: sourceChannel,
|
|
1242
|
+
interfaceId: sourceInterface,
|
|
1243
|
+
} satisfies NonHostProxyTransportMetadata);
|
|
1073
1244
|
|
|
1074
1245
|
const conversation = await smDeps.getOrCreateConversation(
|
|
1075
1246
|
mapping.conversationId,
|
|
@@ -1139,12 +1310,13 @@ export async function handleSendMessage(
|
|
|
1139
1310
|
conversation,
|
|
1140
1311
|
);
|
|
1141
1312
|
const isInteractive = isInteractiveInterface(sourceInterface);
|
|
1142
|
-
// Only create
|
|
1143
|
-
//
|
|
1144
|
-
//
|
|
1313
|
+
// Only create each host proxy for interfaces that support the matching
|
|
1314
|
+
// capability. macOS supports all four; the chrome-extension interface only
|
|
1315
|
+
// supports host_browser. Non-desktop conversations (CLI, channels, headless)
|
|
1316
|
+
// fall back to local execution.
|
|
1145
1317
|
// Set the proxy BEFORE updateClient so updateClient's call to
|
|
1146
1318
|
// hostBashProxy.updateSender targets the correct (new) proxy.
|
|
1147
|
-
if (supportsHostProxy(sourceInterface)) {
|
|
1319
|
+
if (supportsHostProxy(sourceInterface, "host_bash")) {
|
|
1148
1320
|
// Reuse the existing proxy if the conversation is actively processing a
|
|
1149
1321
|
// host bash request to avoid orphaning in-flight requests.
|
|
1150
1322
|
if (!conversation.isProcessing() || !conversation.hostBashProxy) {
|
|
@@ -1153,12 +1325,92 @@ export async function handleSendMessage(
|
|
|
1153
1325
|
});
|
|
1154
1326
|
conversation.setHostBashProxy(proxy);
|
|
1155
1327
|
}
|
|
1328
|
+
} else if (!conversation.isProcessing()) {
|
|
1329
|
+
conversation.setHostBashProxy(undefined);
|
|
1330
|
+
}
|
|
1331
|
+
// For the chrome-extension interface we route host_browser_request /
|
|
1332
|
+
// host_browser_cancel frames through the in-process ChromeExtensionRegistry
|
|
1333
|
+
// to the WebSocket opened against /v1/browser-relay by the connected
|
|
1334
|
+
// extension, instead of the SSE/onEvent hub used by macOS. The registry
|
|
1335
|
+
// lookup is keyed by the JWT-derived actor principal id, which the
|
|
1336
|
+
// runtime captured at WebSocket upgrade time.
|
|
1337
|
+
//
|
|
1338
|
+
// A single guardian may have multiple parallel extension installs
|
|
1339
|
+
// connected at once (two Chrome profiles, two desktops). The registry
|
|
1340
|
+
// tracks them under (guardianId, clientInstanceId) pairs and the
|
|
1341
|
+
// default `send(guardianId, msg)` path routes to whichever instance
|
|
1342
|
+
// has the most recent activity — typically the one the user is
|
|
1343
|
+
// currently driving. Pinning to a specific instance can be done via
|
|
1344
|
+
// `sendToInstance` if a caller ever needs it.
|
|
1345
|
+
//
|
|
1346
|
+
// macOS (and any other interface that supports host_browser in the
|
|
1347
|
+
// future via the SSE hub) keeps using `onEvent` — see the else branch.
|
|
1348
|
+
const browserProxySendToClient: (msg: ServerMessage) => void =
|
|
1349
|
+
sourceInterface === "chrome-extension"
|
|
1350
|
+
? (msg) => {
|
|
1351
|
+
// Resolve the guardian principal id at send time rather than
|
|
1352
|
+
// capturing it from the POST-time authContext. This closure can be
|
|
1353
|
+
// re-fired on queue drain — if a different actor's POST lands while
|
|
1354
|
+
// the queue is still draining an earlier turn, a captured
|
|
1355
|
+
// authContext.actorPrincipalId would mis-route the earlier turn's
|
|
1356
|
+
// host_browser frames to the *new* actor. Preferring
|
|
1357
|
+
// conversation.trustContext?.guardianPrincipalId makes the routing
|
|
1358
|
+
// follow the conversation's bound guardian, which is stable across
|
|
1359
|
+
// subsequent POSTs. Falls back to the per-POST authContext for
|
|
1360
|
+
// turns that haven't been bound to a trust context yet.
|
|
1361
|
+
const gid =
|
|
1362
|
+
conversation.trustContext?.guardianPrincipalId ??
|
|
1363
|
+
authContext.actorPrincipalId;
|
|
1364
|
+
if (!gid) {
|
|
1365
|
+
// No guardian identity on this turn — nothing to route to.
|
|
1366
|
+
// The proxy will observe this via its try/catch and surface a
|
|
1367
|
+
// transport error back to the caller.
|
|
1368
|
+
throw new Error(
|
|
1369
|
+
"chrome-extension host_browser send skipped: no guardianId on AuthContext",
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
const ok = getChromeExtensionRegistry().send(gid, msg);
|
|
1373
|
+
if (!ok) {
|
|
1374
|
+
throw new Error(
|
|
1375
|
+
`chrome-extension host_browser send failed: no active connection for guardian ${gid}`,
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
: onEvent;
|
|
1380
|
+
// Stash the registry-routed sender on the conversation so queue-drain
|
|
1381
|
+
// restores (which run outside of conversation-routes.ts and only have
|
|
1382
|
+
// access to `sendToClient`) can preserve it when calling
|
|
1383
|
+
// `restoreBrowserProxyAvailability()`. For non-chrome-extension
|
|
1384
|
+
// interfaces the override is cleared so the SSE hub sender is used.
|
|
1385
|
+
if (sourceInterface === "chrome-extension") {
|
|
1386
|
+
conversation.hostBrowserSenderOverride = browserProxySendToClient;
|
|
1387
|
+
} else {
|
|
1388
|
+
conversation.hostBrowserSenderOverride = undefined;
|
|
1389
|
+
}
|
|
1390
|
+
if (supportsHostProxy(sourceInterface, "host_browser")) {
|
|
1391
|
+
if (!conversation.isProcessing() || !conversation.hostBrowserProxy) {
|
|
1392
|
+
const browserProxy = new HostBrowserProxy(
|
|
1393
|
+
browserProxySendToClient,
|
|
1394
|
+
(requestId) => {
|
|
1395
|
+
pendingInteractions.resolve(requestId);
|
|
1396
|
+
},
|
|
1397
|
+
);
|
|
1398
|
+
conversation.setHostBrowserProxy(browserProxy);
|
|
1399
|
+
}
|
|
1400
|
+
} else if (!conversation.isProcessing()) {
|
|
1401
|
+
conversation.setHostBrowserProxy(undefined);
|
|
1402
|
+
}
|
|
1403
|
+
if (supportsHostProxy(sourceInterface, "host_file")) {
|
|
1156
1404
|
if (!conversation.isProcessing() || !conversation.hostFileProxy) {
|
|
1157
1405
|
const fileProxy = new HostFileProxy(onEvent, (requestId) => {
|
|
1158
1406
|
pendingInteractions.resolve(requestId);
|
|
1159
1407
|
});
|
|
1160
1408
|
conversation.setHostFileProxy(fileProxy);
|
|
1161
1409
|
}
|
|
1410
|
+
} else if (!conversation.isProcessing()) {
|
|
1411
|
+
conversation.setHostFileProxy(undefined);
|
|
1412
|
+
}
|
|
1413
|
+
if (supportsHostProxy(sourceInterface, "host_cu")) {
|
|
1162
1414
|
if (!conversation.isProcessing() || !conversation.hostCuProxy) {
|
|
1163
1415
|
const cuProxy = new HostCuProxy(onEvent, (requestId) => {
|
|
1164
1416
|
pendingInteractions.resolve(requestId);
|
|
@@ -1172,19 +1424,41 @@ export async function handleSendMessage(
|
|
|
1172
1424
|
conversation.addPreactivatedSkillId("computer-use");
|
|
1173
1425
|
}
|
|
1174
1426
|
} else if (!conversation.isProcessing()) {
|
|
1175
|
-
conversation.setHostBashProxy(undefined);
|
|
1176
|
-
conversation.setHostFileProxy(undefined);
|
|
1177
1427
|
conversation.setHostCuProxy(undefined);
|
|
1178
1428
|
}
|
|
1179
1429
|
// Wire sendToClient to the SSE hub so all subsystems can reach the HTTP client.
|
|
1180
1430
|
// Called after setHostBashProxy so updateSender targets the current proxy.
|
|
1181
1431
|
// When proxies are preserved during an active turn (non-desktop request while
|
|
1182
|
-
// processing), skip updating proxy senders to avoid degrading them.
|
|
1432
|
+
// processing), skip updating proxy senders to avoid degrading them. The gate
|
|
1433
|
+
// matches the host_bash capability because the legacy "reject send during
|
|
1434
|
+
// host bash" flow is what this is really protecting.
|
|
1183
1435
|
const preservingProxies =
|
|
1184
|
-
conversation.isProcessing() &&
|
|
1436
|
+
conversation.isProcessing() &&
|
|
1437
|
+
!supportsHostProxy(sourceInterface, "host_bash");
|
|
1438
|
+
// hasNoClient must remain `!isInteractive` so downstream tool gating
|
|
1439
|
+
// (`isToolActiveForContext` for HOST_TOOL_NAMES, `createToolExecutor`'s
|
|
1440
|
+
// `isInteractive: !ctx.hasNoClient`) keeps host_bash/host_file/host_cu
|
|
1441
|
+
// tools gated for non-desktop interfaces. The chrome-extension interface
|
|
1442
|
+
// is non-interactive (no SSE prompter UI) but still has a connected client
|
|
1443
|
+
// that can service host_browser_request events; we restore that single
|
|
1444
|
+
// proxy explicitly below without relaxing `hasNoClient`.
|
|
1185
1445
|
conversation.updateClient(onEvent, !isInteractive, {
|
|
1186
1446
|
skipProxySenderUpdate: preservingProxies,
|
|
1187
1447
|
});
|
|
1448
|
+
// For non-interactive interfaces that DO support host_browser
|
|
1449
|
+
// (chrome-extension), explicitly re-enable just the browser proxy. The
|
|
1450
|
+
// helper bypasses the `hasNoClient` gate so the single-capability
|
|
1451
|
+
// chrome-extension turn can drive the browser via CDP without leaking
|
|
1452
|
+
// host_bash/host_file tool availability into tool gating.
|
|
1453
|
+
//
|
|
1454
|
+
// `restoreBrowserProxyAvailability()` reads `hostBrowserSenderOverride`
|
|
1455
|
+
// (set above for chrome-extension) and applies the registry-routed
|
|
1456
|
+
// sender, so the chrome-extension path gets the correct sender here
|
|
1457
|
+
// — including after queue-drain restores run from conversation-process.ts,
|
|
1458
|
+
// which only have access to the conversation instance.
|
|
1459
|
+
if (supportsHostProxy(sourceInterface, "host_browser")) {
|
|
1460
|
+
conversation.restoreBrowserProxyAvailability?.();
|
|
1461
|
+
}
|
|
1188
1462
|
|
|
1189
1463
|
// ── Canned first-greeting fast path ──
|
|
1190
1464
|
// On a completely fresh workspace, skip LLM inference for the macOS
|
|
@@ -4,13 +4,17 @@
|
|
|
4
4
|
* GET /v1/conversation-starters — list conversation starters (chips)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { and, desc, eq, inArray,
|
|
7
|
+
import { and, desc, eq, inArray, sql } from "drizzle-orm";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
10
|
import { getDb } from "../../memory/db.js";
|
|
11
11
|
import { enqueueMemoryJob } from "../../memory/jobs-store.js";
|
|
12
12
|
import { rawGet } from "../../memory/raw-query.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
conversationStarters,
|
|
15
|
+
memoryCheckpoints,
|
|
16
|
+
memoryJobs,
|
|
17
|
+
} from "../../memory/schema.js";
|
|
14
18
|
import type { RouteDefinition } from "../http-router.js";
|
|
15
19
|
|
|
16
20
|
// ---------------------------------------------------------------------------
|
|
@@ -26,6 +30,39 @@ interface StarterItem {
|
|
|
26
30
|
batch: number;
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
const CK_ITEM_COUNT = "conversation_starters:item_count_at_last_gen";
|
|
34
|
+
const CK_LAST_GEN_AT = "conversation_starters:last_gen_at";
|
|
35
|
+
export const CONVERSATION_STARTERS_STALE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
36
|
+
|
|
37
|
+
function checkpointKey(base: string, scopeId: string): string {
|
|
38
|
+
return `${base}:${scopeId}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseCheckpointInt(value: string | undefined): number | null {
|
|
42
|
+
if (!value) return null;
|
|
43
|
+
const parsed = Number.parseInt(value, 10);
|
|
44
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hasActiveConversationStarterJob(
|
|
48
|
+
db: ReturnType<typeof getDb>,
|
|
49
|
+
scopeId: string,
|
|
50
|
+
): boolean {
|
|
51
|
+
return (
|
|
52
|
+
db
|
|
53
|
+
.select({ id: memoryJobs.id })
|
|
54
|
+
.from(memoryJobs)
|
|
55
|
+
.where(
|
|
56
|
+
and(
|
|
57
|
+
eq(memoryJobs.type, "generate_conversation_starters"),
|
|
58
|
+
inArray(memoryJobs.status, ["pending", "running"]),
|
|
59
|
+
sql`json_extract(${memoryJobs.payload}, '$.scopeId') = ${scopeId}`,
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
.get() != null
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
29
66
|
/**
|
|
30
67
|
* Re-order starters so adjacent items have distinct categories wherever
|
|
31
68
|
* possible. Within each category, preserve the original (batch-desc) order.
|
|
@@ -150,14 +187,47 @@ function handleListConversationStarters(url: URL): Response {
|
|
|
150
187
|
|
|
151
188
|
const total = allItems.length;
|
|
152
189
|
|
|
153
|
-
// If starters exist,
|
|
190
|
+
// If starters exist, return them immediately. If the batch is stale or
|
|
191
|
+
// the generation checkpoint is ahead of the current active memory count,
|
|
192
|
+
// kick off a background refresh but keep the existing chips visible.
|
|
154
193
|
if (total > 0) {
|
|
194
|
+
const totalActive =
|
|
195
|
+
rawGet<{ c: number }>(
|
|
196
|
+
`SELECT COUNT(*) AS c FROM memory_graph_nodes WHERE fidelity != 'gone' AND scope_id = ?`,
|
|
197
|
+
scopeId,
|
|
198
|
+
)?.c ?? 0;
|
|
199
|
+
const lastCount = parseCheckpointInt(
|
|
200
|
+
db
|
|
201
|
+
.select({ value: memoryCheckpoints.value })
|
|
202
|
+
.from(memoryCheckpoints)
|
|
203
|
+
.where(eq(memoryCheckpoints.key, checkpointKey(CK_ITEM_COUNT, scopeId)))
|
|
204
|
+
.get()?.value,
|
|
205
|
+
);
|
|
206
|
+
const lastGenAt = parseCheckpointInt(
|
|
207
|
+
db
|
|
208
|
+
.select({ value: memoryCheckpoints.value })
|
|
209
|
+
.from(memoryCheckpoints)
|
|
210
|
+
.where(eq(memoryCheckpoints.key, checkpointKey(CK_LAST_GEN_AT, scopeId)))
|
|
211
|
+
.get()?.value,
|
|
212
|
+
);
|
|
213
|
+
const staleByAge =
|
|
214
|
+
lastGenAt == null ||
|
|
215
|
+
Date.now() - lastGenAt >= CONVERSATION_STARTERS_STALE_TTL_MS;
|
|
216
|
+
const checkpointAhead = lastCount != null && totalActive < lastCount;
|
|
217
|
+
let hasActiveJob = hasActiveConversationStarterJob(db, scopeId);
|
|
218
|
+
const shouldRefresh = staleByAge || checkpointAhead;
|
|
219
|
+
|
|
220
|
+
if (shouldRefresh && !hasActiveJob) {
|
|
221
|
+
enqueueMemoryJob("generate_conversation_starters", { scopeId });
|
|
222
|
+
hasActiveJob = true;
|
|
223
|
+
}
|
|
224
|
+
|
|
155
225
|
const ordered = orderStrongestFirst(allItems);
|
|
156
226
|
const page = ordered.slice(offsetParam, offsetParam + limitParam);
|
|
157
227
|
return Response.json({
|
|
158
228
|
starters: page,
|
|
159
229
|
total,
|
|
160
|
-
status: "ready",
|
|
230
|
+
status: hasActiveJob ? "refreshing" : "ready",
|
|
161
231
|
});
|
|
162
232
|
}
|
|
163
233
|
|
|
@@ -172,17 +242,7 @@ function handleListConversationStarters(url: URL): Response {
|
|
|
172
242
|
}
|
|
173
243
|
|
|
174
244
|
// Memory items exist but no starters yet — ensure a generation job is queued.
|
|
175
|
-
const existing = db
|
|
176
|
-
.select({ id: memoryJobs.id })
|
|
177
|
-
.from(memoryJobs)
|
|
178
|
-
.where(
|
|
179
|
-
and(
|
|
180
|
-
eq(memoryJobs.type, "generate_conversation_starters"),
|
|
181
|
-
inArray(memoryJobs.status, ["pending", "running"]),
|
|
182
|
-
like(memoryJobs.payload, `%"scopeId":"${scopeId}"%`),
|
|
183
|
-
),
|
|
184
|
-
)
|
|
185
|
-
.get();
|
|
245
|
+
const existing = hasActiveConversationStarterJob(db, scopeId);
|
|
186
246
|
|
|
187
247
|
if (!existing) {
|
|
188
248
|
enqueueMemoryJob("generate_conversation_starters", { scopeId });
|
|
@@ -227,7 +287,9 @@ export function conversationStarterRouteDefinitions(): RouteDefinition[] {
|
|
|
227
287
|
.array(z.unknown())
|
|
228
288
|
.describe("Ordered list of starter chips"),
|
|
229
289
|
total: z.number().int().describe("Total number of available starters"),
|
|
230
|
-
status: z
|
|
290
|
+
status: z
|
|
291
|
+
.enum(["ready", "refreshing", "empty", "generating"])
|
|
292
|
+
.describe("One of: ready, refreshing, empty, generating"),
|
|
231
293
|
}),
|
|
232
294
|
},
|
|
233
295
|
];
|
|
@@ -19,13 +19,14 @@ import {
|
|
|
19
19
|
type CanonicalGuardianRequest,
|
|
20
20
|
listPendingRequestsByConversationScope,
|
|
21
21
|
} from "../../memory/canonical-guardian-store.js";
|
|
22
|
+
import { isPermissionControlsV2Enabled } from "../../permissions/v2-consent-policy.js";
|
|
22
23
|
import { requireBoundGuardian } from "../auth/require-bound-guardian.js";
|
|
23
24
|
import type { AuthContext } from "../auth/types.js";
|
|
24
25
|
import { processGuardianDecision } from "../guardian-action-service.js";
|
|
25
26
|
import type { GuardianDecisionPrompt } from "../guardian-decision-types.js";
|
|
26
27
|
import {
|
|
27
28
|
buildDecisionActions,
|
|
28
|
-
|
|
29
|
+
buildOneTimeDecisionActions,
|
|
29
30
|
} from "../guardian-decision-types.js";
|
|
30
31
|
import { httpError } from "../http-errors.js";
|
|
31
32
|
import type { RouteDefinition } from "../http-router.js";
|
|
@@ -98,6 +99,18 @@ export async function handleGuardianActionDecision(
|
|
|
98
99
|
return httpError("BAD_REQUEST", "action is required", 400);
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
if (
|
|
103
|
+
isPermissionControlsV2Enabled() &&
|
|
104
|
+
action !== "approve_once" &&
|
|
105
|
+
action !== "reject"
|
|
106
|
+
) {
|
|
107
|
+
return httpError(
|
|
108
|
+
"FORBIDDEN",
|
|
109
|
+
"permission-controls-v2 only accepts approve_once or reject for guardian actions",
|
|
110
|
+
403,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
101
114
|
// Resolve the actor's guardian principal ID. For JWT-verified actors this
|
|
102
115
|
// comes from the token claims. For dev bypass (HTTP auth disabled) the
|
|
103
116
|
// synthetic "dev-bypass" principal won't match the real guardian binding,
|
|
@@ -193,15 +206,17 @@ export function listGuardianDecisionPrompts(params: {
|
|
|
193
206
|
* Map a canonical guardian request to the client-facing prompt format.
|
|
194
207
|
*
|
|
195
208
|
* Generates kind-specific questionText and action sets:
|
|
196
|
-
* - `tool_approval`: temporal modes (approve_once, approve_10m, approve_conversation) + reject
|
|
209
|
+
* - `tool_approval`: temporal modes (approve_once, approve_10m, approve_conversation) + reject in legacy mode,
|
|
210
|
+
* approve_once + reject only under v2
|
|
197
211
|
* - `pending_question`: approve_once + reject only
|
|
198
212
|
* - `access_request`: approve_once + reject only, with text fallback instructions
|
|
199
213
|
* (request code + "open invite flow")
|
|
200
214
|
* - `tool_grant_request`: approve_once + reject only
|
|
201
215
|
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
216
|
+
* Under permission-controls-v2 we collapse all deterministic guardian action
|
|
217
|
+
* prompts to approve_once + reject only. Outside v2, only `tool_approval`
|
|
218
|
+
* receives temporal modes because time-scoped grants are meaningful only for
|
|
219
|
+
* tool execution.
|
|
205
220
|
*/
|
|
206
221
|
function mapCanonicalRequestToPrompt(
|
|
207
222
|
req: CanonicalGuardianRequest,
|
|
@@ -209,15 +224,11 @@ function mapCanonicalRequestToPrompt(
|
|
|
209
224
|
): GuardianDecisionPrompt {
|
|
210
225
|
const questionText = buildKindAwareQuestionText(req);
|
|
211
226
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
req.kind === "tool_approval"
|
|
227
|
+
const actions = isPermissionControlsV2Enabled()
|
|
228
|
+
? buildOneTimeDecisionActions()
|
|
229
|
+
: req.kind === "tool_approval"
|
|
216
230
|
? buildDecisionActions({ forGuardianOnBehalf: true })
|
|
217
|
-
:
|
|
218
|
-
GUARDIAN_DECISION_ACTIONS.approve_once,
|
|
219
|
-
GUARDIAN_DECISION_ACTIONS.reject,
|
|
220
|
-
];
|
|
231
|
+
: buildOneTimeDecisionActions();
|
|
221
232
|
|
|
222
233
|
const expiresAt = req.expiresAt
|
|
223
234
|
? new Date(req.expiresAt).getTime()
|