@vellumai/assistant 0.6.1 → 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/docker-entrypoint.sh +12 -2
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- 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__/assistant-event-hub.test.ts +30 -0
- 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__/checker.test.ts +104 -170
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- 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 +21 -6
- 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 +169 -0
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -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 -3
- 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__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/log-export-workspace.test.ts +190 -0
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- 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__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -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 +74 -55
- 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__/pkb-autoinject.test.ts +96 -0
- 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 -3
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- 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-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +11 -5
- package/src/__tests__/test-preload.ts +14 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +0 -1
- 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 +62 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -35
- 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 +55 -0
- package/src/cli/__tests__/run-assistant-command.ts +34 -7
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/default-action.ts +68 -1
- 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 +68 -41
- 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 +16 -2
- 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/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- 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 +10 -3
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
- 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 +12 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- 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 +46 -7
- 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 +16 -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 -16
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +6 -2
- package/src/daemon/context-overflow-approval.ts +5 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +74 -19
- package/src/daemon/conversation-attachments.ts +40 -1
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +66 -3
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +159 -20
- package/src/daemon/conversation-surfaces.ts +78 -12
- package/src/daemon/conversation-tool-setup.ts +74 -11
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +227 -11
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -139
- package/src/daemon/handlers/shared.ts +65 -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 +86 -12
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +59 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -6
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/settings.ts +12 -0
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +112 -35
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +14 -0
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +38 -10
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +65 -5
- 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 +177 -18
- package/src/memory/graph/capability-seed.ts +3 -5
- 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/group-crud.ts +25 -9
- 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/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- 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 +5 -5
- 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 +127 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -96
- package/src/prompts/templates/SOUL.md +11 -11
- 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 +24 -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/auth/token-service.ts +8 -0
- 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 +18 -5
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +308 -28
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/group-routes.ts +22 -8
- 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/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +60 -25
- 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/skills/inline-command-runner.ts +12 -14
- 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 -100
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -1
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/skills/sandbox-runner.ts +3 -6
- 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/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +24 -21
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -3
- package/src/util/platform.ts +14 -19
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/029-seed-pkb.ts +1 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +2 -0
- 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
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* POST /v1/conversations — create a new conversation
|
|
5
5
|
* POST /v1/conversations/switch — switch to an existing conversation
|
|
6
6
|
* POST /v1/conversations/fork — fork an existing conversation
|
|
7
|
+
* GET /v1/conversations/:id/host-access — read host access for one conversation
|
|
8
|
+
* PATCH /v1/conversations/:id/host-access — update host access for one conversation
|
|
7
9
|
* PATCH /v1/conversations/:id/name — rename a conversation
|
|
8
10
|
* DELETE /v1/conversations — clear all conversations
|
|
9
11
|
* POST /v1/conversations/:id/wipe — wipe conversation and revert memory
|
|
@@ -21,7 +23,9 @@ import {
|
|
|
21
23
|
countConversationsByScheduleJobId,
|
|
22
24
|
deleteConversation,
|
|
23
25
|
getConversation,
|
|
26
|
+
getConversationHostAccess,
|
|
24
27
|
PRIVATE_CONVERSATION_FORK_ERROR,
|
|
28
|
+
updateConversationHostAccess,
|
|
25
29
|
wipeConversation,
|
|
26
30
|
} from "../../memory/conversation-crud.js";
|
|
27
31
|
import { updateConversationTitle } from "../../memory/conversation-crud.js";
|
|
@@ -34,6 +38,10 @@ import { enqueueMemoryJob } from "../../memory/jobs-store.js";
|
|
|
34
38
|
import { deleteSchedule } from "../../schedule/schedule-store.js";
|
|
35
39
|
import { UserError } from "../../util/errors.js";
|
|
36
40
|
import { getLogger } from "../../util/logger.js";
|
|
41
|
+
import { buildAssistantEvent } from "../assistant-event.js";
|
|
42
|
+
import { assistantEventHub } from "../assistant-event-hub.js";
|
|
43
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
44
|
+
import { requireBoundGuardian } from "../auth/require-bound-guardian.js";
|
|
37
45
|
import { httpError } from "../http-errors.js";
|
|
38
46
|
import type { RouteDefinition } from "../http-router.js";
|
|
39
47
|
|
|
@@ -52,6 +60,7 @@ export interface ConversationManagementDeps {
|
|
|
52
60
|
conversationId: string;
|
|
53
61
|
title: string;
|
|
54
62
|
conversationType: string;
|
|
63
|
+
hostAccess: boolean;
|
|
55
64
|
} | null>;
|
|
56
65
|
renameConversation: (conversationId: string, name: string) => boolean;
|
|
57
66
|
clearAllConversations: () => number;
|
|
@@ -218,6 +227,7 @@ export function conversationManagementRouteDefinitions(
|
|
|
218
227
|
conversationId: z.string(),
|
|
219
228
|
title: z.string(),
|
|
220
229
|
conversationType: z.string(),
|
|
230
|
+
hostAccess: z.boolean(),
|
|
221
231
|
}),
|
|
222
232
|
handler: async ({ req }) => {
|
|
223
233
|
const body = (await req.json()) as {
|
|
@@ -246,6 +256,104 @@ export function conversationManagementRouteDefinitions(
|
|
|
246
256
|
title: result.title,
|
|
247
257
|
conversationType:
|
|
248
258
|
result.conversationType === "private" ? "private" : "standard",
|
|
259
|
+
hostAccess: result.hostAccess,
|
|
260
|
+
});
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
endpoint: "conversations/:id/host-access",
|
|
265
|
+
method: "GET",
|
|
266
|
+
policyKey: "conversations/host-access:GET",
|
|
267
|
+
summary: "Get conversation host access",
|
|
268
|
+
description: "Return whether the conversation can use host tools.",
|
|
269
|
+
tags: ["conversations"],
|
|
270
|
+
responseBody: z.object({
|
|
271
|
+
conversationId: z.string(),
|
|
272
|
+
hostAccess: z.boolean(),
|
|
273
|
+
}),
|
|
274
|
+
handler: ({ params }) => {
|
|
275
|
+
const resolvedId = resolveConversationId(params.id) ?? params.id;
|
|
276
|
+
const conversation = getConversation(resolvedId);
|
|
277
|
+
if (!conversation) {
|
|
278
|
+
return httpError(
|
|
279
|
+
"NOT_FOUND",
|
|
280
|
+
`Conversation ${params.id} not found`,
|
|
281
|
+
404,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
return Response.json({
|
|
285
|
+
conversationId: conversation.id,
|
|
286
|
+
hostAccess: getConversationHostAccess(conversation.id),
|
|
287
|
+
});
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
endpoint: "conversations/:id/host-access",
|
|
292
|
+
method: "PATCH",
|
|
293
|
+
policyKey: "conversations/host-access",
|
|
294
|
+
summary: "Update conversation host access",
|
|
295
|
+
description: "Enable or disable host access for a conversation.",
|
|
296
|
+
tags: ["conversations"],
|
|
297
|
+
requestBody: z.object({
|
|
298
|
+
hostAccess: z.boolean(),
|
|
299
|
+
}),
|
|
300
|
+
responseBody: z.object({
|
|
301
|
+
conversationId: z.string(),
|
|
302
|
+
hostAccess: z.boolean(),
|
|
303
|
+
}),
|
|
304
|
+
handler: async ({ req, params, authContext }) => {
|
|
305
|
+
const guardianError = requireBoundGuardian(authContext);
|
|
306
|
+
if (guardianError) return guardianError;
|
|
307
|
+
|
|
308
|
+
const rawBody = (await req.json()) as unknown;
|
|
309
|
+
if (
|
|
310
|
+
rawBody == null ||
|
|
311
|
+
typeof rawBody !== "object" ||
|
|
312
|
+
Array.isArray(rawBody)
|
|
313
|
+
) {
|
|
314
|
+
return httpError("BAD_REQUEST", "Invalid request body", 400);
|
|
315
|
+
}
|
|
316
|
+
const body = rawBody as { hostAccess?: unknown };
|
|
317
|
+
if (typeof body.hostAccess !== "boolean") {
|
|
318
|
+
return httpError("BAD_REQUEST", "Missing hostAccess boolean", 400);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const resolvedId = resolveConversationId(params.id) ?? params.id;
|
|
322
|
+
const conversation = getConversation(resolvedId);
|
|
323
|
+
if (!conversation) {
|
|
324
|
+
return httpError(
|
|
325
|
+
"NOT_FOUND",
|
|
326
|
+
`Conversation ${params.id} not found`,
|
|
327
|
+
404,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const nextHostAccess = body.hostAccess;
|
|
332
|
+
if (conversation.hostAccess !== (nextHostAccess ? 1 : 0)) {
|
|
333
|
+
updateConversationHostAccess(resolvedId, nextHostAccess);
|
|
334
|
+
assistantEventHub
|
|
335
|
+
.publish(
|
|
336
|
+
buildAssistantEvent(
|
|
337
|
+
DAEMON_INTERNAL_ASSISTANT_ID,
|
|
338
|
+
{
|
|
339
|
+
type: "conversation_host_access_updated",
|
|
340
|
+
conversationId: resolvedId,
|
|
341
|
+
hostAccess: nextHostAccess,
|
|
342
|
+
},
|
|
343
|
+
resolvedId,
|
|
344
|
+
),
|
|
345
|
+
)
|
|
346
|
+
.catch((err) => {
|
|
347
|
+
log.warn(
|
|
348
|
+
{ err, conversationId: resolvedId },
|
|
349
|
+
"Failed to publish conversation_host_access_updated event",
|
|
350
|
+
);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return Response.json({
|
|
355
|
+
conversationId: resolvedId,
|
|
356
|
+
hostAccess: nextHostAccess,
|
|
249
357
|
});
|
|
250
358
|
},
|
|
251
359
|
},
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
isInteractiveInterface,
|
|
18
18
|
parseChannelId,
|
|
19
19
|
parseInterfaceId,
|
|
20
|
+
supportsHostProxy,
|
|
20
21
|
} from "../../channels/types.js";
|
|
21
22
|
import { isHttpAuthDisabled } from "../../config/env.js";
|
|
22
23
|
import { getConfig } from "../../config/loader.js";
|
|
@@ -35,12 +36,13 @@ import {
|
|
|
35
36
|
} from "../../daemon/first-greeting.js";
|
|
36
37
|
import { renderHistoryContent } from "../../daemon/handlers/shared.js";
|
|
37
38
|
import { HostBashProxy } from "../../daemon/host-bash-proxy.js";
|
|
39
|
+
import { HostBrowserProxy } from "../../daemon/host-browser-proxy.js";
|
|
38
40
|
import { HostCuProxy } from "../../daemon/host-cu-proxy.js";
|
|
39
41
|
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
|
|
40
42
|
import type { ServerMessage } from "../../daemon/message-protocol.js";
|
|
41
43
|
import type {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
HostProxyTransportMetadata,
|
|
45
|
+
NonHostProxyTransportMetadata,
|
|
44
46
|
} from "../../daemon/message-types/conversations.js";
|
|
45
47
|
import type { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
|
|
46
48
|
import * as attachmentsStore from "../../memory/attachments-store.js";
|
|
@@ -76,6 +78,7 @@ import { silentlyWithLog } from "../../util/silently.js";
|
|
|
76
78
|
import { buildAssistantEvent } from "../assistant-event.js";
|
|
77
79
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
78
80
|
import type { AuthContext } from "../auth/types.js";
|
|
81
|
+
import { getChromeExtensionRegistry } from "../chrome-extension-registry.js";
|
|
79
82
|
import { bridgeConfirmationRequestToGuardian } from "../confirmation-request-guardian-bridge.js";
|
|
80
83
|
import { routeGuardianReply } from "../guardian-reply-router.js";
|
|
81
84
|
import { healGuardianBindingDrift } from "../guardian-vellum-migration.js";
|
|
@@ -421,8 +424,18 @@ export function handleListMessages(
|
|
|
421
424
|
// pendingToolUses map — otherwise they render as "Unknown" tool calls.
|
|
422
425
|
const mergedMessages = mergeToolResultsIntoAssistantMessages(rawMessages);
|
|
423
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
|
+
|
|
424
437
|
// Parse content blocks and extract text + tool calls
|
|
425
|
-
const parsed =
|
|
438
|
+
const parsed = consolidatedMessages.map((msg) => {
|
|
426
439
|
let content: unknown;
|
|
427
440
|
try {
|
|
428
441
|
content = JSON.parse(msg.content);
|
|
@@ -547,7 +560,13 @@ export function handleListMessages(
|
|
|
547
560
|
// blobs for non-image attachments (documents, audio). Then
|
|
548
561
|
// selectively fetch full data only for images so the client can
|
|
549
562
|
// generate thumbnails for inline display on history restore.
|
|
550
|
-
|
|
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
|
+
);
|
|
551
570
|
if (linked.length > 0) {
|
|
552
571
|
msgAttachments = linked.map((a) => {
|
|
553
572
|
if (a.mimeType.startsWith("image/")) {
|
|
@@ -721,7 +740,10 @@ function mergeToolResultsIntoAssistantMessages(
|
|
|
721
740
|
}
|
|
722
741
|
}
|
|
723
742
|
|
|
724
|
-
// No tool results → pass through unchanged.
|
|
743
|
+
// No tool results → pass through unchanged. System notices are only
|
|
744
|
+
// injected alongside tool results in the agent loop, so a pure user
|
|
745
|
+
// message (no tool_result blocks) should never be filtered — even if
|
|
746
|
+
// the user's text happens to look like a system_notice tag.
|
|
725
747
|
if (toolResultBlocks.length === 0) {
|
|
726
748
|
result.push(msg);
|
|
727
749
|
continue;
|
|
@@ -789,14 +811,158 @@ function mergeToolResultsIntoAssistantMessages(
|
|
|
789
811
|
return result;
|
|
790
812
|
}
|
|
791
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
|
+
|
|
792
958
|
/**
|
|
793
959
|
* Build an `onEvent` callback that publishes every outbound event to the
|
|
794
960
|
* assistant event hub, maintaining ordered delivery through a serial chain.
|
|
795
961
|
*
|
|
796
962
|
* Also registers pending interactions when confirmation_request,
|
|
797
|
-
* secret_request, host_bash_request,
|
|
798
|
-
* through, so standalone approval/result
|
|
799
|
-
* 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.
|
|
800
966
|
*/
|
|
801
967
|
function makeHubPublisher(
|
|
802
968
|
deps: SendMessageDeps,
|
|
@@ -888,6 +1054,12 @@ function makeHubPublisher(
|
|
|
888
1054
|
conversationId,
|
|
889
1055
|
kind: "host_bash",
|
|
890
1056
|
});
|
|
1057
|
+
} else if (msg.type === "host_browser_request") {
|
|
1058
|
+
pendingInteractions.register(msg.requestId, {
|
|
1059
|
+
conversation,
|
|
1060
|
+
conversationId,
|
|
1061
|
+
kind: "host_browser",
|
|
1062
|
+
});
|
|
891
1063
|
} else if (msg.type === "host_file_request") {
|
|
892
1064
|
pendingInteractions.register(msg.requestId, {
|
|
893
1065
|
conversation,
|
|
@@ -1054,18 +1226,21 @@ export async function handleSendMessage(
|
|
|
1054
1226
|
|
|
1055
1227
|
// Build transport metadata from the request so the daemon can inject
|
|
1056
1228
|
// host environment hints (home directory, username) into the LLM context.
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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);
|
|
1069
1244
|
|
|
1070
1245
|
const conversation = await smDeps.getOrCreateConversation(
|
|
1071
1246
|
mapping.conversationId,
|
|
@@ -1135,12 +1310,13 @@ export async function handleSendMessage(
|
|
|
1135
1310
|
conversation,
|
|
1136
1311
|
);
|
|
1137
1312
|
const isInteractive = isInteractiveInterface(sourceInterface);
|
|
1138
|
-
// Only create
|
|
1139
|
-
//
|
|
1140
|
-
//
|
|
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.
|
|
1141
1317
|
// Set the proxy BEFORE updateClient so updateClient's call to
|
|
1142
1318
|
// hostBashProxy.updateSender targets the correct (new) proxy.
|
|
1143
|
-
if (sourceInterface
|
|
1319
|
+
if (supportsHostProxy(sourceInterface, "host_bash")) {
|
|
1144
1320
|
// Reuse the existing proxy if the conversation is actively processing a
|
|
1145
1321
|
// host bash request to avoid orphaning in-flight requests.
|
|
1146
1322
|
if (!conversation.isProcessing() || !conversation.hostBashProxy) {
|
|
@@ -1149,12 +1325,92 @@ export async function handleSendMessage(
|
|
|
1149
1325
|
});
|
|
1150
1326
|
conversation.setHostBashProxy(proxy);
|
|
1151
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")) {
|
|
1152
1404
|
if (!conversation.isProcessing() || !conversation.hostFileProxy) {
|
|
1153
1405
|
const fileProxy = new HostFileProxy(onEvent, (requestId) => {
|
|
1154
1406
|
pendingInteractions.resolve(requestId);
|
|
1155
1407
|
});
|
|
1156
1408
|
conversation.setHostFileProxy(fileProxy);
|
|
1157
1409
|
}
|
|
1410
|
+
} else if (!conversation.isProcessing()) {
|
|
1411
|
+
conversation.setHostFileProxy(undefined);
|
|
1412
|
+
}
|
|
1413
|
+
if (supportsHostProxy(sourceInterface, "host_cu")) {
|
|
1158
1414
|
if (!conversation.isProcessing() || !conversation.hostCuProxy) {
|
|
1159
1415
|
const cuProxy = new HostCuProxy(onEvent, (requestId) => {
|
|
1160
1416
|
pendingInteractions.resolve(requestId);
|
|
@@ -1168,19 +1424,41 @@ export async function handleSendMessage(
|
|
|
1168
1424
|
conversation.addPreactivatedSkillId("computer-use");
|
|
1169
1425
|
}
|
|
1170
1426
|
} else if (!conversation.isProcessing()) {
|
|
1171
|
-
conversation.setHostBashProxy(undefined);
|
|
1172
|
-
conversation.setHostFileProxy(undefined);
|
|
1173
1427
|
conversation.setHostCuProxy(undefined);
|
|
1174
1428
|
}
|
|
1175
1429
|
// Wire sendToClient to the SSE hub so all subsystems can reach the HTTP client.
|
|
1176
1430
|
// Called after setHostBashProxy so updateSender targets the current proxy.
|
|
1177
1431
|
// When proxies are preserved during an active turn (non-desktop request while
|
|
1178
|
-
// 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.
|
|
1179
1435
|
const preservingProxies =
|
|
1180
|
-
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`.
|
|
1181
1445
|
conversation.updateClient(onEvent, !isInteractive, {
|
|
1182
1446
|
skipProxySenderUpdate: preservingProxies,
|
|
1183
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
|
+
}
|
|
1184
1462
|
|
|
1185
1463
|
// ── Canned first-greeting fast path ──
|
|
1186
1464
|
// On a completely fresh workspace, skip LLM inference for the macOS
|
|
@@ -1338,6 +1616,8 @@ export async function handleSendMessage(
|
|
|
1338
1616
|
...(body.automated === true ? { automated: true } : {}),
|
|
1339
1617
|
},
|
|
1340
1618
|
{ isInteractive },
|
|
1619
|
+
undefined, // displayContent
|
|
1620
|
+
transport,
|
|
1341
1621
|
);
|
|
1342
1622
|
if (enqueueResult.rejected) {
|
|
1343
1623
|
return Response.json(
|