@vellumai/assistant 0.6.2 → 0.6.4
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/ARCHITECTURE.md +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +41 -49
- package/bunfig.toml +3 -0
- package/docs/architecture/memory.md +1 -1
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +1111 -86
- package/package.json +40 -42
- package/scripts/generate-openapi.ts +0 -2
- package/scripts/test.sh +73 -18
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- 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__/auto-analysis-end-to-end.test.ts +550 -0
- package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
- package/src/__tests__/browser-fill-credential.test.ts +240 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +1000 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -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 +1248 -224
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-overflow-approval.test.ts +16 -1
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -1
- package/src/__tests__/conversation-agent-loop.test.ts +99 -3
- 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 +290 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +12 -4
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +946 -62
- package/src/__tests__/conversation-routes-disk-view.test.ts +275 -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 +324 -46
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- 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-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +6 -3
- package/src/__tests__/credential-vault-unit.test.ts +383 -7
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +42 -18
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +222 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +386 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +2 -0
- package/src/__tests__/gemini-provider.test.ts +66 -2
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +738 -359
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +528 -49
- package/src/__tests__/headless-browser-read-tools.test.ts +274 -100
- package/src/__tests__/headless-browser-snapshot.test.ts +250 -77
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +145 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +596 -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 +423 -0
- package/src/__tests__/host-cu-proxy.test.ts +166 -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__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +67 -8
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +109 -7
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/oauth-apps-routes.test.ts +18 -12
- package/src/__tests__/oauth-cli.test.ts +709 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +118 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +147 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +52 -14
- package/src/__tests__/oauth-store.test.ts +1465 -176
- package/src/__tests__/oauth2-gateway-transport.test.ts +460 -26
- package/src/__tests__/onboarding-template-contract.test.ts +81 -70
- package/src/__tests__/openai-provider.test.ts +178 -2
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- 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 +32 -3
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +343 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- 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__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +8 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +801 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +738 -0
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +576 -16
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- 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 +184 -27
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +25 -5
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -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__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +55 -9
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/backup/__tests__/backup-key.test.ts +152 -0
- package/src/backup/__tests__/backup-worker.test.ts +767 -0
- package/src/backup/__tests__/list-snapshots.test.ts +87 -0
- package/src/backup/__tests__/local-writer.test.ts +218 -0
- package/src/backup/__tests__/offsite-writer.test.ts +641 -0
- package/src/backup/__tests__/paths.test.ts +300 -0
- package/src/backup/__tests__/restore.test.ts +498 -0
- package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
- package/src/backup/__tests__/stream-crypt.test.ts +228 -0
- package/src/backup/backup-key.ts +137 -0
- package/src/backup/backup-worker.ts +459 -0
- package/src/backup/list-snapshots.ts +147 -0
- package/src/backup/local-writer.ts +133 -0
- package/src/backup/offsite-writer.ts +222 -0
- package/src/backup/paths.ts +226 -0
- package/src/backup/restore.ts +322 -0
- package/src/backup/snapshot-lock.ts +431 -0
- package/src/backup/stream-crypt.ts +263 -0
- 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/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +69 -3
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +3 -4
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +273 -16
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +56 -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 +32 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +330 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +117 -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/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +215 -36
- package/src/cli/commands/oauth/shared.ts +7 -6
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +254 -0
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -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 +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +141 -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/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +5 -2
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +54 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +9 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -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/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +38 -0
- package/src/config/env.ts +49 -4
- package/src/config/feature-flag-registry.json +85 -14
- package/src/config/loader.ts +82 -13
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +81 -15
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +112 -0
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +52 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -1
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +177 -0
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +61 -10
- package/src/credential-execution/approval-bridge.ts +49 -15
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +195 -0
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/context-overflow-approval.ts +5 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +23 -2
- package/src/daemon/conversation-agent-loop.ts +153 -42
- package/src/daemon/conversation-attachments.ts +40 -0
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +622 -13
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +128 -36
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +131 -40
- package/src/daemon/conversation-tool-setup.ts +99 -8
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +292 -16
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +13 -141
- package/src/daemon/handlers/shared.ts +80 -0
- package/src/daemon/handlers/skills.ts +483 -44
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +192 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +179 -28
- package/src/daemon/message-protocol.ts +13 -0
- package/src/daemon/message-types/conversations.ts +89 -14
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +19 -5
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +26 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +439 -14
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +15 -0
- package/src/daemon/transport-hints.ts +5 -24
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +30 -20
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/mcp/client.ts +59 -24
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +31 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +122 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -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 +18 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -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/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +67 -40
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +137 -60
- 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/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +12 -0
- package/src/memory/migrations/registry.ts +16 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/oauth.ts +21 -13
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +25 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +26 -9
- package/src/oauth/byo-connection.ts +10 -8
- package/src/oauth/connect-orchestrator.ts +25 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +22 -18
- package/src/oauth/connection.ts +3 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +223 -100
- package/src/oauth/platform-connection.test.ts +101 -3
- package/src/oauth/platform-connection.ts +56 -35
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +133 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +76 -38
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -105
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -60
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +10 -1
- package/src/runtime/AGENTS.md +65 -0
- package/src/runtime/__tests__/agent-wake.test.ts +831 -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/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -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 +48 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +33 -9
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +368 -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 +815 -75
- package/src/runtime/http-types.ts +6 -2
- 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 +198 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +7 -0
- package/src/runtime/migrations/migration-wizard.ts +23 -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 +96 -1
- package/src/runtime/migrations/vbundle-importer.ts +89 -5
- package/src/runtime/pending-interactions.ts +18 -13
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +556 -0
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -141
- package/src/runtime/routes/conversation-management-routes.ts +223 -0
- package/src/runtime/routes/conversation-routes.ts +598 -103
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +268 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +262 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/log-export-routes.ts +42 -22
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +122 -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 +31 -102
- package/src/runtime/routes/skills-routes.ts +128 -9
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +38 -9
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- 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/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +57 -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 +68 -29
- package/src/security/secure-keys.ts +143 -27
- package/src/security/token-manager.ts +31 -10
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +554 -0
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +169 -40
- 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/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +1787 -342
- package/src/tools/browser/browser-manager.ts +81 -12
- package/src/tools/browser/browser-mode-constants.ts +12 -0
- package/src/tools/browser/browser-mode.ts +92 -0
- package/src/tools/browser/browser-status-constants.ts +33 -0
- 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 +1263 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +359 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1993 -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 +1007 -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 +744 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +868 -0
- package/src/tools/browser/cdp-client/errors.ts +49 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +148 -0
- package/src/tools/browser/cdp-client/factory.ts +914 -0
- package/src/tools/browser/cdp-client/index.ts +28 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +120 -0
- package/src/tools/credentials/vault.ts +35 -6
- 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/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- 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/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- 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 +15 -0
- package/src/tools/terminal/shell.ts +36 -20
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +19 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +63 -24
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +31 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- 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/cli/commands/browser-relay.ts +0 -536
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/email/guardrails.ts +0 -221
- package/src/email/provider.ts +0 -117
- package/src/email/providers/agentmail.ts +0 -361
- package/src/email/providers/index.ts +0 -65
- package/src/email/service.ts +0 -384
- package/src/email/types.ts +0 -126
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/tools/browser/chrome-cdp.ts +0 -239
- package/src/tools/system/set-permission-mode.ts +0 -103
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the /v1/browser-extension-pair capability-token pair endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Method/host/origin enforcement (405, 403, 400, 401)
|
|
6
|
+
* - Native-host marker header requirement (403 when missing)
|
|
7
|
+
* - Browser-origin rejection (non-allowlisted Origin header -> 403)
|
|
8
|
+
* - Strict per-peer rate limiting (10/min, then 429)
|
|
9
|
+
* - Audit logging field shape for denied attempts
|
|
10
|
+
* - Successful mint on allowed origin (200) for both the preferred
|
|
11
|
+
* `extensionOrigin` body field and the legacy `origin` alias
|
|
12
|
+
* - `expiresAt` response field is an ISO 8601 string matching what the
|
|
13
|
+
* native messaging helper validates
|
|
14
|
+
* - IPv6 loopback `Host` header variants (bracketed and bare) are
|
|
15
|
+
* accepted
|
|
16
|
+
* - Issued token round-trips through `verifyHostBrowserCapability`
|
|
17
|
+
* - Tampered tokens fail verification
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { randomBytes } from "node:crypto";
|
|
21
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
resetCapabilityTokenSecretForTests,
|
|
25
|
+
setCapabilityTokenSecretForTests,
|
|
26
|
+
verifyHostBrowserCapability,
|
|
27
|
+
} from "../capability-tokens.js";
|
|
28
|
+
import {
|
|
29
|
+
ALLOWED_EXTENSION_ORIGINS,
|
|
30
|
+
handleBrowserExtensionPair,
|
|
31
|
+
NATIVE_HOST_MARKER_HEADER,
|
|
32
|
+
NATIVE_HOST_MARKER_VALUE,
|
|
33
|
+
parseHostHeader,
|
|
34
|
+
resetPairRateLimiterForTests,
|
|
35
|
+
} from "../routes/browser-extension-pair-routes.js";
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Test helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
type ServerWithRequestIP = {
|
|
42
|
+
requestIP(
|
|
43
|
+
req: Request,
|
|
44
|
+
): { address: string; family: string; port: number } | null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function mockServer(address: string): ServerWithRequestIP {
|
|
48
|
+
return {
|
|
49
|
+
requestIP: () => ({ address, family: "IPv4", port: 0 }),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const loopbackServer = mockServer("127.0.0.1");
|
|
54
|
+
const lanPeerServer = mockServer("192.168.1.10");
|
|
55
|
+
const publicPeerServer = mockServer("203.0.113.50");
|
|
56
|
+
|
|
57
|
+
const ALLOWED_ORIGIN = (() => {
|
|
58
|
+
const first = Array.from(ALLOWED_EXTENSION_ORIGINS)[0];
|
|
59
|
+
if (!first) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"ALLOWED_EXTENSION_ORIGINS must contain at least one extension origin for tests",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return first;
|
|
65
|
+
})();
|
|
66
|
+
const ALLOWED_ORIGIN_BARE = ALLOWED_ORIGIN.endsWith("/")
|
|
67
|
+
? ALLOWED_ORIGIN.slice(0, -1)
|
|
68
|
+
: ALLOWED_ORIGIN;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Build a pair request. By default includes the native-host marker
|
|
72
|
+
* header so existing tests exercising other invariants continue to
|
|
73
|
+
* pass. Tests that want to exercise the marker-header gate pass
|
|
74
|
+
* `nativeHost: false` (or an explicit override value).
|
|
75
|
+
*/
|
|
76
|
+
function buildRequest(
|
|
77
|
+
options: {
|
|
78
|
+
method?: string;
|
|
79
|
+
body?: unknown;
|
|
80
|
+
host?: string | null;
|
|
81
|
+
origin?: string;
|
|
82
|
+
forwardedFor?: string;
|
|
83
|
+
rawBody?: string;
|
|
84
|
+
/**
|
|
85
|
+
* When `false`, omits the native-host marker header entirely.
|
|
86
|
+
* When a string, sets the header to that value (for testing
|
|
87
|
+
* unexpected values). Defaults to including the expected value.
|
|
88
|
+
*/
|
|
89
|
+
nativeHost?: boolean | string;
|
|
90
|
+
} = {},
|
|
91
|
+
): Request {
|
|
92
|
+
const headers = new Headers();
|
|
93
|
+
if (options.host !== null) {
|
|
94
|
+
headers.set("host", options.host ?? "127.0.0.1:8765");
|
|
95
|
+
}
|
|
96
|
+
if (options.forwardedFor) {
|
|
97
|
+
headers.set("x-forwarded-for", options.forwardedFor);
|
|
98
|
+
}
|
|
99
|
+
if (options.origin !== undefined) {
|
|
100
|
+
headers.set("origin", options.origin);
|
|
101
|
+
}
|
|
102
|
+
if (options.nativeHost === undefined || options.nativeHost === true) {
|
|
103
|
+
headers.set(NATIVE_HOST_MARKER_HEADER, NATIVE_HOST_MARKER_VALUE);
|
|
104
|
+
} else if (typeof options.nativeHost === "string") {
|
|
105
|
+
headers.set(NATIVE_HOST_MARKER_HEADER, options.nativeHost);
|
|
106
|
+
}
|
|
107
|
+
// else: nativeHost === false — omit the header entirely.
|
|
108
|
+
let bodyStr: string | undefined;
|
|
109
|
+
if (options.rawBody !== undefined) {
|
|
110
|
+
bodyStr = options.rawBody;
|
|
111
|
+
headers.set("content-type", "application/json");
|
|
112
|
+
} else if (options.body !== undefined) {
|
|
113
|
+
bodyStr = JSON.stringify(options.body);
|
|
114
|
+
headers.set("content-type", "application/json");
|
|
115
|
+
}
|
|
116
|
+
return new Request("http://127.0.0.1:8765/v1/browser-extension-pair", {
|
|
117
|
+
method: options.method ?? "POST",
|
|
118
|
+
headers,
|
|
119
|
+
body: bodyStr,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Tests
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
describe("handleBrowserExtensionPair", () => {
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
resetCapabilityTokenSecretForTests();
|
|
130
|
+
setCapabilityTokenSecretForTests(randomBytes(32));
|
|
131
|
+
// Reset the per-peer rate limiter so one test's burst of requests
|
|
132
|
+
// cannot leak budget into the next test.
|
|
133
|
+
resetPairRateLimiterForTests();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("rejects non-POST methods with 405", async () => {
|
|
137
|
+
const req = buildRequest({
|
|
138
|
+
method: "GET",
|
|
139
|
+
body: {
|
|
140
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
144
|
+
expect(res.status).toBe(405);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("rejects non-loopback peer with 403", async () => {
|
|
148
|
+
const req = buildRequest({
|
|
149
|
+
body: {
|
|
150
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
const res = await handleBrowserExtensionPair(req, publicPeerServer);
|
|
154
|
+
expect(res.status).toBe(403);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("rejects LAN peer (not loopback) with 403", async () => {
|
|
158
|
+
const req = buildRequest({
|
|
159
|
+
body: {
|
|
160
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
const res = await handleBrowserExtensionPair(req, lanPeerServer);
|
|
164
|
+
expect(res.status).toBe(403);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("rejects request with non-loopback Host header", async () => {
|
|
168
|
+
const req = buildRequest({
|
|
169
|
+
body: {
|
|
170
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
171
|
+
},
|
|
172
|
+
host: "vellum.example.com",
|
|
173
|
+
});
|
|
174
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
175
|
+
expect(res.status).toBe(403);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("rejects request with x-forwarded-for header", async () => {
|
|
179
|
+
const req = buildRequest({
|
|
180
|
+
body: {
|
|
181
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
182
|
+
},
|
|
183
|
+
forwardedFor: "1.2.3.4",
|
|
184
|
+
});
|
|
185
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
186
|
+
expect(res.status).toBe(403);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
190
|
+
// Native-host marker header enforcement
|
|
191
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
test("rejects request missing native-host marker header with 403", async () => {
|
|
194
|
+
const req = buildRequest({
|
|
195
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
196
|
+
nativeHost: false,
|
|
197
|
+
});
|
|
198
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
199
|
+
expect(res.status).toBe(403);
|
|
200
|
+
const payload = (await res.json()) as {
|
|
201
|
+
error?: { code?: string; message?: string };
|
|
202
|
+
};
|
|
203
|
+
expect(payload.error?.code).toBe("FORBIDDEN");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("rejects request with wrong native-host marker header value", async () => {
|
|
207
|
+
const req = buildRequest({
|
|
208
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
209
|
+
nativeHost: "bogus",
|
|
210
|
+
});
|
|
211
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
212
|
+
expect(res.status).toBe(403);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("rejects request with empty native-host marker header value", async () => {
|
|
216
|
+
const req = buildRequest({
|
|
217
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
218
|
+
nativeHost: "",
|
|
219
|
+
});
|
|
220
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
221
|
+
expect(res.status).toBe(403);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
225
|
+
// Browser-origin rejection
|
|
226
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
test("rejects request with a non-allowlisted Origin header", async () => {
|
|
229
|
+
const req = buildRequest({
|
|
230
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
231
|
+
origin: "https://evil.example.com",
|
|
232
|
+
});
|
|
233
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
234
|
+
expect(res.status).toBe(403);
|
|
235
|
+
const payload = (await res.json()) as {
|
|
236
|
+
error?: { code?: string };
|
|
237
|
+
};
|
|
238
|
+
expect(payload.error?.code).toBe("FORBIDDEN");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("rejects request with http://localhost Origin header (browser-originated)", async () => {
|
|
242
|
+
// A web page served from http://localhost:8080 that POSTs to the
|
|
243
|
+
// pair endpoint would attach this Origin header. The endpoint must
|
|
244
|
+
// refuse it even though the host itself is loopback — a local web
|
|
245
|
+
// page in another browser tab is NOT the native messaging helper.
|
|
246
|
+
const req = buildRequest({
|
|
247
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
248
|
+
origin: "http://localhost:8080",
|
|
249
|
+
});
|
|
250
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
251
|
+
expect(res.status).toBe(403);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("accepts request with no Origin header (native-host default)", async () => {
|
|
255
|
+
// Node fetch does not set an Origin header unless explicitly told
|
|
256
|
+
// to — the native messaging helper's `fetch(...)` call therefore
|
|
257
|
+
// ships without one. This is the common-case allowed path.
|
|
258
|
+
const req = buildRequest({
|
|
259
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
260
|
+
// origin intentionally omitted
|
|
261
|
+
});
|
|
262
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
263
|
+
expect(res.status).toBe(200);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("accepts request when Origin header equals an allowlisted extension origin", async () => {
|
|
267
|
+
// The allowlist stores entries with a trailing slash. Browsers'
|
|
268
|
+
// Origin headers never carry a path segment, so both the exact
|
|
269
|
+
// match and the bare form should succeed.
|
|
270
|
+
const reqExact = buildRequest({
|
|
271
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
272
|
+
origin: ALLOWED_ORIGIN,
|
|
273
|
+
});
|
|
274
|
+
expect(
|
|
275
|
+
(await handleBrowserExtensionPair(reqExact, loopbackServer)).status,
|
|
276
|
+
).toBe(200);
|
|
277
|
+
|
|
278
|
+
const reqBare = buildRequest({
|
|
279
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
280
|
+
origin: ALLOWED_ORIGIN_BARE,
|
|
281
|
+
});
|
|
282
|
+
expect(
|
|
283
|
+
(await handleBrowserExtensionPair(reqBare, loopbackServer)).status,
|
|
284
|
+
).toBe(200);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
288
|
+
// Rate limiting
|
|
289
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
test("rate limits successive pair requests (429 after burst)", async () => {
|
|
292
|
+
// Fire a tight burst of requests and assert that once the per-peer
|
|
293
|
+
// budget is exhausted, the endpoint returns 429 with the standard
|
|
294
|
+
// error envelope and a Retry-After hint. The rate limiter budget
|
|
295
|
+
// is 10/min per peer IP; we send 12 to ensure we hit it.
|
|
296
|
+
const results: number[] = [];
|
|
297
|
+
for (let i = 0; i < 12; i++) {
|
|
298
|
+
const req = buildRequest({
|
|
299
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
300
|
+
});
|
|
301
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
302
|
+
results.push(res.status);
|
|
303
|
+
// Drain the body so any async internal state settles (and so
|
|
304
|
+
// we don't leak unconsumed Response bodies).
|
|
305
|
+
await res.text();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// The first 10 requests should succeed (200). The 11th and 12th
|
|
309
|
+
// should be rate limited (429).
|
|
310
|
+
const successes = results.filter((s) => s === 200).length;
|
|
311
|
+
const rateLimited = results.filter((s) => s === 429).length;
|
|
312
|
+
expect(successes).toBe(10);
|
|
313
|
+
expect(rateLimited).toBe(2);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("rate-limited response carries Retry-After and RATE_LIMITED error code", async () => {
|
|
317
|
+
// Exhaust the budget.
|
|
318
|
+
for (let i = 0; i < 10; i++) {
|
|
319
|
+
const req = buildRequest({
|
|
320
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
321
|
+
});
|
|
322
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
323
|
+
await res.text();
|
|
324
|
+
}
|
|
325
|
+
// The next request should be rate limited.
|
|
326
|
+
const req = buildRequest({
|
|
327
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
328
|
+
});
|
|
329
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
330
|
+
expect(res.status).toBe(429);
|
|
331
|
+
const retryAfter = res.headers.get("Retry-After");
|
|
332
|
+
expect(retryAfter).not.toBeNull();
|
|
333
|
+
// Retry-After should be a positive integer of seconds.
|
|
334
|
+
expect(Number(retryAfter)).toBeGreaterThan(0);
|
|
335
|
+
expect(res.headers.get("X-RateLimit-Limit")).toBe("10");
|
|
336
|
+
expect(res.headers.get("X-RateLimit-Remaining")).toBe("0");
|
|
337
|
+
const payload = (await res.json()) as {
|
|
338
|
+
error?: { code?: string };
|
|
339
|
+
};
|
|
340
|
+
expect(payload.error?.code).toBe("RATE_LIMITED");
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("native-host marker check runs BEFORE the rate limiter (unmarked requests don't burn budget)", async () => {
|
|
344
|
+
// Security invariant: an unmarked drive-by POST from a malicious
|
|
345
|
+
// webpage must NOT consume the legitimate 10/min quota. If the
|
|
346
|
+
// rate limiter ran first, a cross-origin page could issue 10
|
|
347
|
+
// unmarked requests per minute and starve the native messaging
|
|
348
|
+
// helper's real pair attempts with 429s until the window reset.
|
|
349
|
+
//
|
|
350
|
+
// We send a large burst of UNMARKED requests (well beyond the
|
|
351
|
+
// 10/min budget). Every single one must return 403 (missing
|
|
352
|
+
// marker), NOT 429 — because the marker check runs first and
|
|
353
|
+
// unmarked requests are supposed to short-circuit the handler
|
|
354
|
+
// without touching the limiter at all.
|
|
355
|
+
const unmarkedResults: number[] = [];
|
|
356
|
+
for (let i = 0; i < 30; i++) {
|
|
357
|
+
const req = buildRequest({
|
|
358
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
359
|
+
nativeHost: false, // no marker header
|
|
360
|
+
});
|
|
361
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
362
|
+
unmarkedResults.push(res.status);
|
|
363
|
+
await res.text();
|
|
364
|
+
}
|
|
365
|
+
// Every unmarked request should be 403. If any 429 appeared, the
|
|
366
|
+
// limiter was burning budget on unauthenticated probes.
|
|
367
|
+
expect(unmarkedResults.every((s) => s === 403)).toBe(true);
|
|
368
|
+
expect(unmarkedResults.some((s) => s === 429)).toBe(false);
|
|
369
|
+
|
|
370
|
+
// After 30 unmarked requests, the legitimate native-messaging
|
|
371
|
+
// helper must still have its full 10/min budget intact. Issue 10
|
|
372
|
+
// MARKED requests: all 10 should succeed. The 11th should be the
|
|
373
|
+
// first 429 — proving the limiter only counts marker-carrying
|
|
374
|
+
// requests.
|
|
375
|
+
const markedResults: number[] = [];
|
|
376
|
+
for (let i = 0; i < 11; i++) {
|
|
377
|
+
const req = buildRequest({
|
|
378
|
+
body: { extensionOrigin: ALLOWED_ORIGIN },
|
|
379
|
+
});
|
|
380
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
381
|
+
markedResults.push(res.status);
|
|
382
|
+
await res.text();
|
|
383
|
+
}
|
|
384
|
+
expect(markedResults.slice(0, 10).every((s) => s === 200)).toBe(true);
|
|
385
|
+
expect(markedResults[10]).toBe(429);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
389
|
+
// Body validation
|
|
390
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
391
|
+
|
|
392
|
+
test("returns 400 when body is missing", async () => {
|
|
393
|
+
const req = buildRequest({});
|
|
394
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
395
|
+
expect(res.status).toBe(400);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("returns 400 when body is malformed JSON", async () => {
|
|
399
|
+
const req = buildRequest({ rawBody: "{not json" });
|
|
400
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
401
|
+
expect(res.status).toBe(400);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("returns 400 when extensionOrigin is missing", async () => {
|
|
405
|
+
const req = buildRequest({ body: {} });
|
|
406
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
407
|
+
expect(res.status).toBe(400);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test("returns 400 when extensionOrigin is not a string", async () => {
|
|
411
|
+
const req = buildRequest({ body: { extensionOrigin: 42 } });
|
|
412
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
413
|
+
expect(res.status).toBe(400);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("returns 400 when legacy origin field is not a string", async () => {
|
|
417
|
+
const req = buildRequest({ body: { origin: 42 } });
|
|
418
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
419
|
+
expect(res.status).toBe(400);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("returns 401 when extensionOrigin is not on the allowlist", async () => {
|
|
423
|
+
const req = buildRequest({
|
|
424
|
+
body: { extensionOrigin: "chrome-extension://not-allowed/" },
|
|
425
|
+
});
|
|
426
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
427
|
+
expect(res.status).toBe(401);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("returns 200 with a valid token for the preferred extensionOrigin field", async () => {
|
|
431
|
+
const req = buildRequest({
|
|
432
|
+
body: {
|
|
433
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
437
|
+
expect(res.status).toBe(200);
|
|
438
|
+
|
|
439
|
+
const payload = (await res.json()) as {
|
|
440
|
+
token: string;
|
|
441
|
+
expiresAt: string;
|
|
442
|
+
guardianId: string;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
expect(typeof payload.token).toBe("string");
|
|
446
|
+
expect(payload.token.length).toBeGreaterThan(0);
|
|
447
|
+
|
|
448
|
+
// expiresAt must be an ISO 8601 string (matching what the
|
|
449
|
+
// chrome-extension/native-host helper validates) and must be in
|
|
450
|
+
// the future.
|
|
451
|
+
expect(typeof payload.expiresAt).toBe("string");
|
|
452
|
+
expect(payload.expiresAt).toMatch(
|
|
453
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,
|
|
454
|
+
);
|
|
455
|
+
const expiresAtMs = Date.parse(payload.expiresAt);
|
|
456
|
+
expect(Number.isNaN(expiresAtMs)).toBe(false);
|
|
457
|
+
expect(expiresAtMs).toBeGreaterThan(Date.now());
|
|
458
|
+
|
|
459
|
+
expect(typeof payload.guardianId).toBe("string");
|
|
460
|
+
expect(payload.guardianId.length).toBeGreaterThan(0);
|
|
461
|
+
|
|
462
|
+
// Token should round-trip through verifyHostBrowserCapability.
|
|
463
|
+
const claims = verifyHostBrowserCapability(payload.token);
|
|
464
|
+
expect(claims).not.toBeNull();
|
|
465
|
+
expect(claims?.capability).toBe("host_browser_command");
|
|
466
|
+
expect(claims?.guardianId).toBe(payload.guardianId);
|
|
467
|
+
// The numeric claim expiry should match the ISO response field.
|
|
468
|
+
expect(claims?.expiresAt).toBe(expiresAtMs);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("returns 200 using the legacy `origin` field for backwards compat", async () => {
|
|
472
|
+
const req = buildRequest({
|
|
473
|
+
body: { origin: ALLOWED_ORIGIN },
|
|
474
|
+
});
|
|
475
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
476
|
+
expect(res.status).toBe(200);
|
|
477
|
+
const payload = (await res.json()) as {
|
|
478
|
+
token: string;
|
|
479
|
+
expiresAt: string;
|
|
480
|
+
};
|
|
481
|
+
expect(typeof payload.token).toBe("string");
|
|
482
|
+
expect(typeof payload.expiresAt).toBe("string");
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
test("prefers extensionOrigin over legacy origin when both are provided", async () => {
|
|
486
|
+
// extensionOrigin is on the allowlist, `origin` is not — so the
|
|
487
|
+
// request must succeed because we honor `extensionOrigin` first.
|
|
488
|
+
const req = buildRequest({
|
|
489
|
+
body: {
|
|
490
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
491
|
+
origin: "chrome-extension://not-allowed/",
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
495
|
+
expect(res.status).toBe(200);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test("accepts loopback Host header variants", async () => {
|
|
499
|
+
const variants = [
|
|
500
|
+
"localhost:8765",
|
|
501
|
+
"127.0.0.1:8765",
|
|
502
|
+
"127.0.0.1",
|
|
503
|
+
"localhost",
|
|
504
|
+
"127.1.2.3:8765",
|
|
505
|
+
"[::1]:8765",
|
|
506
|
+
"[::1]",
|
|
507
|
+
"::1",
|
|
508
|
+
];
|
|
509
|
+
for (const host of variants) {
|
|
510
|
+
// Reset the limiter between iterations so the last few variants
|
|
511
|
+
// don't fall over the 10/min budget.
|
|
512
|
+
resetPairRateLimiterForTests();
|
|
513
|
+
const req = buildRequest({
|
|
514
|
+
body: {
|
|
515
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
516
|
+
},
|
|
517
|
+
host,
|
|
518
|
+
});
|
|
519
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
520
|
+
expect(res.status).toBe(200);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test("rejects malformed bracketed Host header", async () => {
|
|
525
|
+
const req = buildRequest({
|
|
526
|
+
body: {
|
|
527
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
528
|
+
},
|
|
529
|
+
host: "[::1", // missing closing bracket
|
|
530
|
+
});
|
|
531
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
532
|
+
expect(res.status).toBe(403);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test("rejects bracketed Host header with junk after closing bracket", async () => {
|
|
536
|
+
// Defensive against `[::1]attacker.com`-style injection: the parser
|
|
537
|
+
// used to silently truncate at the first `]` and treat the rest as
|
|
538
|
+
// the hostname, which would let an attacker spoof a non-loopback
|
|
539
|
+
// host while still passing the loopback Host header check.
|
|
540
|
+
const req = buildRequest({
|
|
541
|
+
body: {
|
|
542
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
543
|
+
},
|
|
544
|
+
host: "[::1]attacker.com",
|
|
545
|
+
});
|
|
546
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
547
|
+
expect(res.status).toBe(403);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test("rejects non-loopback IPv6 Host header", async () => {
|
|
551
|
+
const req = buildRequest({
|
|
552
|
+
body: {
|
|
553
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
554
|
+
},
|
|
555
|
+
host: "[2001:db8::1]:8765",
|
|
556
|
+
});
|
|
557
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
558
|
+
expect(res.status).toBe(403);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test("parseHostHeader handles IPv4, IPv6, and bracketed forms", () => {
|
|
562
|
+
expect(parseHostHeader("localhost:8765")).toBe("localhost");
|
|
563
|
+
expect(parseHostHeader("127.0.0.1:8765")).toBe("127.0.0.1");
|
|
564
|
+
expect(parseHostHeader("127.0.0.1")).toBe("127.0.0.1");
|
|
565
|
+
expect(parseHostHeader("[::1]:8765")).toBe("::1");
|
|
566
|
+
expect(parseHostHeader("[::1]")).toBe("::1");
|
|
567
|
+
expect(parseHostHeader("::1")).toBe("::1");
|
|
568
|
+
expect(parseHostHeader("[2001:db8::1]:443")).toBe("2001:db8::1");
|
|
569
|
+
expect(parseHostHeader("[::1")).toBeNull();
|
|
570
|
+
expect(parseHostHeader("")).toBeNull();
|
|
571
|
+
// Anything after the closing bracket that isn't an optional ":port"
|
|
572
|
+
// must be rejected — otherwise `[::1]attacker.com` would slip past
|
|
573
|
+
// the loopback check by parsing as `::1`.
|
|
574
|
+
expect(parseHostHeader("[::1]attacker.com")).toBeNull();
|
|
575
|
+
expect(parseHostHeader("[::1]extra")).toBeNull();
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test("tampered tokens fail verification", async () => {
|
|
579
|
+
const req = buildRequest({
|
|
580
|
+
body: {
|
|
581
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
585
|
+
expect(res.status).toBe(200);
|
|
586
|
+
|
|
587
|
+
const payload = (await res.json()) as { token: string };
|
|
588
|
+
const originalToken = payload.token;
|
|
589
|
+
|
|
590
|
+
// Modify the signature: flip the last character.
|
|
591
|
+
const [head, sig] = originalToken.split(".");
|
|
592
|
+
const lastChar = sig.slice(-1);
|
|
593
|
+
const replacement = lastChar === "A" ? "B" : "A";
|
|
594
|
+
const tamperedToken = `${head}.${sig.slice(0, -1)}${replacement}`;
|
|
595
|
+
|
|
596
|
+
expect(verifyHostBrowserCapability(tamperedToken)).toBeNull();
|
|
597
|
+
// The original token should still verify.
|
|
598
|
+
expect(verifyHostBrowserCapability(originalToken)).not.toBeNull();
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
test("tokens minted with a different secret fail verification", async () => {
|
|
602
|
+
// Mint a token, then swap the secret — verification should fail.
|
|
603
|
+
const req = buildRequest({
|
|
604
|
+
body: {
|
|
605
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
609
|
+
expect(res.status).toBe(200);
|
|
610
|
+
const payload = (await res.json()) as { token: string };
|
|
611
|
+
|
|
612
|
+
// Swap secret and re-verify.
|
|
613
|
+
setCapabilityTokenSecretForTests(randomBytes(32));
|
|
614
|
+
expect(verifyHostBrowserCapability(payload.token)).toBeNull();
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
test("rejects tampered payload even with matching signature length", async () => {
|
|
618
|
+
const req = buildRequest({
|
|
619
|
+
body: {
|
|
620
|
+
extensionOrigin: ALLOWED_ORIGIN,
|
|
621
|
+
},
|
|
622
|
+
});
|
|
623
|
+
const res = await handleBrowserExtensionPair(req, loopbackServer);
|
|
624
|
+
expect(res.status).toBe(200);
|
|
625
|
+
const payload = (await res.json()) as { token: string };
|
|
626
|
+
const [head, sig] = payload.token.split(".");
|
|
627
|
+
|
|
628
|
+
// Swap the payload for a different base64url value of equivalent shape.
|
|
629
|
+
const bogusPayload = Buffer.from(
|
|
630
|
+
JSON.stringify({
|
|
631
|
+
capability: "host_browser_command",
|
|
632
|
+
guardianId: "attacker",
|
|
633
|
+
nonce: "00".repeat(16),
|
|
634
|
+
expiresAt: Date.now() + 60_000,
|
|
635
|
+
}),
|
|
636
|
+
"utf8",
|
|
637
|
+
)
|
|
638
|
+
.toString("base64")
|
|
639
|
+
.replace(/\+/g, "-")
|
|
640
|
+
.replace(/\//g, "_")
|
|
641
|
+
.replace(/=+$/, "");
|
|
642
|
+
|
|
643
|
+
const tampered = `${bogusPayload}.${sig}`;
|
|
644
|
+
// Keep `head` referenced so the test reads naturally even though we
|
|
645
|
+
// do not use it after tampering.
|
|
646
|
+
expect(head).toBeTruthy();
|
|
647
|
+
expect(verifyHostBrowserCapability(tampered)).toBeNull();
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// ---------------------------------------------------------------------------
|
|
652
|
+
// parseHostHeader — table-driven unit tests for IPv6 / port / malformed edge
|
|
653
|
+
// cases. The handler-level tests above cover end-to-end accept/reject of the
|
|
654
|
+
// pair endpoint; these tests pin down the parser contract itself so that a
|
|
655
|
+
// future refactor of `parseHostHeader` cannot silently change its semantics.
|
|
656
|
+
//
|
|
657
|
+
// Important: `parseHostHeader` does NOT lowercase the hostname — case
|
|
658
|
+
// normalization happens later in `isLoopbackHostHeader`. The tests below
|
|
659
|
+
// reflect that.
|
|
660
|
+
// ---------------------------------------------------------------------------
|
|
661
|
+
describe("parseHostHeader", () => {
|
|
662
|
+
// [input, expected-parseHostHeader-output]. `null` means "malformed".
|
|
663
|
+
const cases: Array<[string, string | null]> = [
|
|
664
|
+
// IPv4 with port
|
|
665
|
+
["127.0.0.1:7821", "127.0.0.1"],
|
|
666
|
+
// Bare IPv4
|
|
667
|
+
["127.0.0.1", "127.0.0.1"],
|
|
668
|
+
// IPv4 in 127.0.0.0/8 range
|
|
669
|
+
["127.1.2.3:7821", "127.1.2.3"],
|
|
670
|
+
// IPv6 with brackets and port
|
|
671
|
+
["[::1]:7821", "::1"],
|
|
672
|
+
// IPv6 with brackets, no port
|
|
673
|
+
["[::1]", "::1"],
|
|
674
|
+
// Bare IPv6 (no brackets, no port) — two or more colons, no brackets,
|
|
675
|
+
// so treated as a whole IPv6 literal rather than split at the first colon.
|
|
676
|
+
["::1", "::1"],
|
|
677
|
+
// Non-loopback IPv6 with brackets
|
|
678
|
+
["[2001:db8::1]:443", "2001:db8::1"],
|
|
679
|
+
// Non-loopback IPv6, bare (multi-colon → treated as IPv6 literal)
|
|
680
|
+
["2001:db8::1", "2001:db8::1"],
|
|
681
|
+
// Hostname with port
|
|
682
|
+
["localhost:7821", "localhost"],
|
|
683
|
+
// Bare hostname
|
|
684
|
+
["localhost", "localhost"],
|
|
685
|
+
// Mixed-case hostname — the parser preserves case; downstream
|
|
686
|
+
// `isLoopbackHostHeader` is responsible for case folding.
|
|
687
|
+
["LocalHost:7821", "LocalHost"],
|
|
688
|
+
// Empty string
|
|
689
|
+
["", null],
|
|
690
|
+
// Malformed: content after the closing bracket that isn't `:port`.
|
|
691
|
+
// Critical security case: `[::1]attacker.com` would slip a non-loopback
|
|
692
|
+
// hostname past a naive parser that truncates at `]`.
|
|
693
|
+
["[::1]attacker.com", null],
|
|
694
|
+
["[::1]extra", null],
|
|
695
|
+
// Malformed: unbalanced brackets (missing closing `]`)
|
|
696
|
+
["[::1", null],
|
|
697
|
+
// Malformed: unbalanced brackets (missing opening `[`) — the leading
|
|
698
|
+
// character is not `[`, so the bare-host path runs; `"]":"port"` has
|
|
699
|
+
// two colons so it's treated as an IPv6 literal (garbage in, garbage
|
|
700
|
+
// out for this edge case — documented for visibility).
|
|
701
|
+
["::1]:7821", "::1]:7821"],
|
|
702
|
+
// Empty brackets: after `[` we see `]` at index 1, after `]` is `:7821`
|
|
703
|
+
// which is a valid port, so the parser returns the substring between
|
|
704
|
+
// the brackets — the empty string. `isLoopbackHostHeader` then rejects
|
|
705
|
+
// it because `""` is not a loopback address.
|
|
706
|
+
["[]:7821", ""],
|
|
707
|
+
// Empty brackets, no port
|
|
708
|
+
["[]", ""],
|
|
709
|
+
];
|
|
710
|
+
for (const [input, expected] of cases) {
|
|
711
|
+
test(`parseHostHeader(${JSON.stringify(input)}) returns ${JSON.stringify(expected)}`, () => {
|
|
712
|
+
expect(parseHostHeader(input)).toBe(expected);
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
});
|