@vellumai/assistant 0.6.3 → 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 +5 -13
- 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/openapi.yaml +982 -72
- package/package.json +4 -6
- package/scripts/generate-openapi.ts +0 -1
- package/scripts/test.sh +73 -18
- 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__/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 +11 -0
- 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 +138 -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-schema.test.ts +1013 -66
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- 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 +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +98 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-history-web-search.test.ts +11 -4
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- 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 +901 -60
- package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
- 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-tool-setup-batch-authorized.test.ts +226 -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-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/credential-vault-unit.test.ts +379 -3
- package/src/__tests__/credentials-cli.test.ts +40 -16
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- 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__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
- package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +64 -0
- 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__/headless-browser-interactions.test.ts +43 -0
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +142 -5
- package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
- 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 +0 -5
- package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
- package/src/__tests__/host-cu-proxy.test.ts +0 -5
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- 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__/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-export-http.test.ts +6 -6
- package/src/__tests__/migration-import-commit-http.test.ts +8 -6
- 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__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +2 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/oauth-store.test.ts +85 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
- package/src/__tests__/onboarding-template-contract.test.ts +6 -13
- package/src/__tests__/openai-provider.test.ts +176 -0
- 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-unsubscribe.test.ts +31 -2
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -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__/search-skills-unified.test.ts +118 -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 +5 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- 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 +276 -145
- package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
- 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 +564 -1
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +112 -26
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +18 -7
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- 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__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- 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__/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/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +43 -3
- 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/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/types.ts +16 -0
- 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 +0 -1
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +255 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +15 -0
- package/src/cli/commands/oauth/shared.ts +2 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
- 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/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/references/CUSTOM_ROUTES.md +37 -1
- 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 +2 -2
- package/src/config/bundled-skills/gmail/SKILL.md +53 -7
- 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 +2 -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/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 +24 -0
- package/src/config/env.ts +34 -10
- package/src/config/feature-flag-registry.json +46 -14
- package/src/config/loader.ts +26 -12
- package/src/config/schema.ts +35 -10
- 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 +47 -1
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/services.ts +44 -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 -0
- 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 +3 -2
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +45 -12
- 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 +17 -8
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +101 -24
- 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 +581 -19
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +11 -1
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +94 -4
- package/src/daemon/conversation-tool-setup.ts +25 -0
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation.ts +86 -28
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +4 -1
- package/src/daemon/handlers/shared.ts +22 -0
- package/src/daemon/handlers/skills.ts +321 -77
- package/src/daemon/host-browser-proxy.ts +2 -1
- package/src/daemon/lifecycle.ts +122 -25
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/conversations.ts +34 -1
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +14 -0
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +16 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +347 -2
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +9 -0
- 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 +12 -3
- 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/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 +1 -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 +99 -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/db-init.ts +6 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/graph/conversation-graph-memory.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 +27 -18
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- 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 +92 -56
- 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 +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/oauth.ts +3 -0
- 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/__tests__/identity-verifier.test.ts +1 -0
- package/src/oauth/byo-connection.test.ts +18 -1
- package/src/oauth/byo-connection.ts +3 -1
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -2
- package/src/oauth/connection.ts +2 -0
- package/src/oauth/oauth-store.ts +9 -0
- package/src/oauth/platform-connection.test.ts +98 -0
- package/src/oauth/platform-connection.ts +52 -31
- package/src/oauth/seed-providers.ts +7 -0
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/trust-store.ts +3 -3
- 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 +59 -18
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- 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 -61
- 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 +9 -0
- package/src/runtime/AGENTS.md +43 -1
- package/src/runtime/__tests__/agent-wake.test.ts +831 -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/auth/__tests__/route-policy.test.ts +40 -0
- package/src/runtime/auth/route-policy.ts +30 -5
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +10 -10
- 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 +38 -2
- package/src/runtime/http-server.ts +395 -10
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +1 -0
- package/src/runtime/migrations/migration-wizard.ts +1 -0
- package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
- package/src/runtime/migrations/vbundle-importer.ts +34 -0
- package/src/runtime/pending-interactions.ts +0 -11
- 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/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 +82 -23
- 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 -142
- package/src/runtime/routes/conversation-management-routes.ts +115 -0
- package/src/runtime/routes/conversation-routes.ts +367 -146
- package/src/runtime/routes/filing-routes.ts +93 -0
- 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 +3 -14
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +3 -17
- 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/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/migration-routes.ts +40 -5
- package/src/runtime/routes/settings-routes.ts +22 -5
- package/src/runtime/routes/skills-routes.ts +76 -7
- package/src/runtime/routes/stt-routes.ts +233 -0
- 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 +30 -2
- 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/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 +50 -0
- package/src/security/oauth2.ts +26 -4
- package/src/security/secure-keys.ts +25 -2
- package/src/security/token-manager.ts +8 -0
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +64 -2
- 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 +38 -14
- 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/browser-execution.ts +1163 -23
- package/src/tools/browser/browser-manager.ts +45 -0
- 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__/cdp-inspect-client.test.ts +393 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
- package/src/tools/browser/cdp-client/errors.ts +15 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
- package/src/tools/browser/cdp-client/factory.ts +797 -87
- package/src/tools/browser/cdp-client/index.ts +16 -2
- package/src/tools/browser/cdp-client/types.ts +68 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- 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/terminal/safe-env.ts +10 -2
- package/src/tools/terminal/shell.ts +15 -4
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +17 -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 +54 -10
- 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 +13 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/cli/commands/browser-relay.ts +0 -466
- 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/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/runtime/routes/browser-cdp-routes.ts +0 -229
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { markdownToEmailHtml } from "../email/html-renderer.js";
|
|
4
|
+
|
|
5
|
+
describe("markdownToEmailHtml", () => {
|
|
6
|
+
test("wraps plain text in email template", () => {
|
|
7
|
+
const result = markdownToEmailHtml("Hello world");
|
|
8
|
+
expect(result).toContain("<!DOCTYPE html>");
|
|
9
|
+
expect(result).toContain("<p>Hello world</p>");
|
|
10
|
+
expect(result).toContain("max-width:600px");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("converts markdown bold to <strong>", () => {
|
|
14
|
+
const result = markdownToEmailHtml("This is **bold** text");
|
|
15
|
+
expect(result).toContain("<strong>bold</strong>");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("converts markdown links", () => {
|
|
19
|
+
const result = markdownToEmailHtml("Visit [Vellum](https://vellum.ai)");
|
|
20
|
+
expect(result).toContain('<a href="https://vellum.ai">Vellum</a>');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("converts markdown lists", () => {
|
|
24
|
+
const result = markdownToEmailHtml("- item one\n- item two\n- item three");
|
|
25
|
+
expect(result).toContain("<li>item one</li>");
|
|
26
|
+
expect(result).toContain("<li>item two</li>");
|
|
27
|
+
expect(result).toContain("<ul>");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("converts markdown code blocks", () => {
|
|
31
|
+
const result = markdownToEmailHtml("```\nconsole.log('hi')\n```");
|
|
32
|
+
expect(result).toContain("<code>");
|
|
33
|
+
expect(result).toContain("console.log(");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("converts line breaks (GFM breaks mode)", () => {
|
|
37
|
+
const result = markdownToEmailHtml("line one\nline two");
|
|
38
|
+
expect(result).toContain("<br>");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("returns raw HTML input as-is", () => {
|
|
42
|
+
const rawHtml = "<div>Already HTML</div>";
|
|
43
|
+
const result = markdownToEmailHtml(rawHtml);
|
|
44
|
+
expect(result).toBe(rawHtml);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("handles empty string", () => {
|
|
48
|
+
const result = markdownToEmailHtml("");
|
|
49
|
+
expect(result).toBe("");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("handles multiline markdown email", () => {
|
|
53
|
+
const md = `Hi there,
|
|
54
|
+
|
|
55
|
+
Thanks for reaching out! Here's what I found:
|
|
56
|
+
|
|
57
|
+
1. **First point** — some details
|
|
58
|
+
2. **Second point** — more details
|
|
59
|
+
|
|
60
|
+
Let me know if you need anything else.
|
|
61
|
+
|
|
62
|
+
Best,
|
|
63
|
+
Assistant`;
|
|
64
|
+
|
|
65
|
+
const result = markdownToEmailHtml(md);
|
|
66
|
+
expect(result).toContain("<!DOCTYPE html>");
|
|
67
|
+
expect(result).toContain("<strong>First point</strong>");
|
|
68
|
+
expect(result).toContain("<ol>");
|
|
69
|
+
expect(result).toContain("<li>");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -1,51 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for the email invite adapter.
|
|
3
3
|
*
|
|
4
|
-
* Verifies that the email adapter resolves the assistant's
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Verifies that the email adapter resolves the assistant's email address
|
|
5
|
+
* from workspace config and falls back to `undefined` when no address
|
|
6
|
+
* is configured.
|
|
7
7
|
*/
|
|
8
8
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
9
9
|
|
|
10
|
-
import { resolveAdapterHandle } from "../runtime/channel-invite-transport.js";
|
|
11
|
-
import { emailInviteAdapter } from "../runtime/channel-invite-transports/email.js";
|
|
12
|
-
|
|
13
10
|
// ---------------------------------------------------------------------------
|
|
14
|
-
// Mock the
|
|
11
|
+
// Mock the config loader
|
|
15
12
|
// ---------------------------------------------------------------------------
|
|
16
13
|
|
|
17
|
-
let
|
|
18
|
-
|
|
19
|
-
mock.module("../
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
let mockConfig: Record<string, unknown> = {};
|
|
15
|
+
|
|
16
|
+
mock.module("../config/loader.js", () => ({
|
|
17
|
+
loadRawConfig: () => mockConfig,
|
|
18
|
+
getNestedValue: (obj: Record<string, unknown>, path: string) => {
|
|
19
|
+
const keys = path.split(".");
|
|
20
|
+
let current: unknown = obj;
|
|
21
|
+
for (const key of keys) {
|
|
22
|
+
if (current == null || typeof current !== "object") return undefined;
|
|
23
|
+
current = (current as Record<string, unknown>)[key];
|
|
24
|
+
}
|
|
25
|
+
return current;
|
|
26
|
+
},
|
|
27
|
+
getConfig: () => ({}),
|
|
28
|
+
saveRawConfig: () => {},
|
|
29
|
+
setNestedValue: () => {},
|
|
25
30
|
}));
|
|
26
31
|
|
|
32
|
+
import { resolveAdapterHandle } from "../runtime/channel-invite-transport.js";
|
|
33
|
+
import { emailInviteAdapter } from "../runtime/channel-invite-transports/email.js";
|
|
34
|
+
|
|
27
35
|
// ---------------------------------------------------------------------------
|
|
28
36
|
// Tests
|
|
29
37
|
// ---------------------------------------------------------------------------
|
|
30
38
|
|
|
31
39
|
describe("emailInviteAdapter", () => {
|
|
32
40
|
beforeEach(() => {
|
|
33
|
-
|
|
41
|
+
mockConfig = {};
|
|
34
42
|
});
|
|
35
43
|
|
|
36
44
|
afterEach(() => {
|
|
37
|
-
|
|
45
|
+
mockConfig = {};
|
|
38
46
|
});
|
|
39
47
|
|
|
40
48
|
test("returns configured email address via resolveChannelHandleAsync", async () => {
|
|
41
|
-
|
|
49
|
+
mockConfig = { email: { address: "hello@vellum.me" } };
|
|
50
|
+
|
|
51
|
+
const handle = await resolveAdapterHandle(emailInviteAdapter);
|
|
52
|
+
expect(handle).toBe("hello@vellum.me");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("returns undefined when no address is configured", async () => {
|
|
56
|
+
mockConfig = {};
|
|
42
57
|
|
|
43
58
|
const handle = await resolveAdapterHandle(emailInviteAdapter);
|
|
44
|
-
expect(handle).
|
|
59
|
+
expect(handle).toBeUndefined();
|
|
45
60
|
});
|
|
46
61
|
|
|
47
|
-
test("returns undefined when
|
|
48
|
-
|
|
62
|
+
test("returns undefined when email.address is empty string", async () => {
|
|
63
|
+
mockConfig = { email: { address: "" } };
|
|
49
64
|
|
|
50
65
|
const handle = await resolveAdapterHandle(emailInviteAdapter);
|
|
51
66
|
expect(handle).toBeUndefined();
|
|
@@ -56,7 +71,6 @@ describe("emailInviteAdapter", () => {
|
|
|
56
71
|
});
|
|
57
72
|
|
|
58
73
|
test("does not define sync resolveChannelHandle", () => {
|
|
59
|
-
// The email adapter uses the async path exclusively
|
|
60
74
|
expect(emailInviteAdapter.resolveChannelHandle).toBeUndefined();
|
|
61
75
|
});
|
|
62
76
|
|
|
@@ -64,14 +78,4 @@ describe("emailInviteAdapter", () => {
|
|
|
64
78
|
expect(emailInviteAdapter.buildShareLink).toBeUndefined();
|
|
65
79
|
expect(emailInviteAdapter.extractInboundToken).toBeUndefined();
|
|
66
80
|
});
|
|
67
|
-
|
|
68
|
-
test("returns config fallback address when provider has no inboxes", async () => {
|
|
69
|
-
// Simulates the config fallback: provider returns no inboxes, but
|
|
70
|
-
// email.address is set in workspace config. The service's
|
|
71
|
-
// getPrimaryInboxAddress() should return the configured address.
|
|
72
|
-
mockPrimaryAddress = "configured@example.com";
|
|
73
|
-
|
|
74
|
-
const handle = await resolveAdapterHandle(emailInviteAdapter);
|
|
75
|
-
expect(handle).toBe("configured@example.com");
|
|
76
|
-
});
|
|
77
81
|
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavioral test for the generic CLI→daemon event bridge.
|
|
3
|
+
*
|
|
4
|
+
* Callers write a JSON-encoded {@link ServerMessage} to
|
|
5
|
+
* `<signalsDir>/emit-event`. {@link handleEmitEventSignal} reads that
|
|
6
|
+
* payload and republishes it through the {@link assistantEventHub} so
|
|
7
|
+
* SSE subscribers receive it.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
14
|
+
import type { AssistantEvent } from "../runtime/assistant-event.js";
|
|
15
|
+
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
16
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
17
|
+
import { handleEmitEventSignal } from "../signals/emit-event.js";
|
|
18
|
+
import { getSignalsDir } from "../util/platform.js";
|
|
19
|
+
|
|
20
|
+
function signalPath(): string {
|
|
21
|
+
return join(getSignalsDir(), "emit-event");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const subscriptions: Array<{ dispose(): void }> = [];
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
for (const sub of subscriptions.splice(0)) {
|
|
28
|
+
sub.dispose();
|
|
29
|
+
}
|
|
30
|
+
const path = signalPath();
|
|
31
|
+
if (existsSync(path)) {
|
|
32
|
+
rmSync(path, { force: true });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("handleEmitEventSignal", () => {
|
|
37
|
+
test("reads a ServerMessage from the signal file and publishes it to the event hub", async () => {
|
|
38
|
+
mkdirSync(getSignalsDir(), { recursive: true });
|
|
39
|
+
|
|
40
|
+
const payload: ServerMessage = { type: "tasks_changed" };
|
|
41
|
+
|
|
42
|
+
writeFileSync(signalPath(), JSON.stringify(payload), "utf-8");
|
|
43
|
+
|
|
44
|
+
const received: AssistantEvent[] = [];
|
|
45
|
+
let resolveDelivered: (() => void) | null = null;
|
|
46
|
+
const delivered = new Promise<void>((resolve) => {
|
|
47
|
+
resolveDelivered = resolve;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
subscriptions.push(
|
|
51
|
+
assistantEventHub.subscribe(
|
|
52
|
+
{ assistantId: DAEMON_INTERNAL_ASSISTANT_ID },
|
|
53
|
+
(event) => {
|
|
54
|
+
received.push(event);
|
|
55
|
+
resolveDelivered?.();
|
|
56
|
+
},
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
handleEmitEventSignal();
|
|
61
|
+
|
|
62
|
+
await delivered;
|
|
63
|
+
|
|
64
|
+
expect(received).toHaveLength(1);
|
|
65
|
+
const event = received[0];
|
|
66
|
+
expect(event.assistantId).toBe(DAEMON_INTERNAL_ASSISTANT_ID);
|
|
67
|
+
expect(event.message).toEqual(payload);
|
|
68
|
+
expect(typeof event.id).toBe("string");
|
|
69
|
+
expect(typeof event.emittedAt).toBe("string");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* (not duplicated across runtime/tests/docs).
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
14
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
15
|
+
import { homedir } from "node:os";
|
|
15
16
|
import { join, resolve } from "node:path";
|
|
16
17
|
import { describe, expect, test } from "bun:test";
|
|
17
18
|
|
|
@@ -21,8 +22,14 @@ const repoRoot = resolve(__dirname, "..", "..", "..");
|
|
|
21
22
|
const CANONICAL_CONFIG_REL_PATH =
|
|
22
23
|
"meta/browser-extension/chrome-extension-allowlist.json";
|
|
23
24
|
const CANONICAL_CONFIG_ABS_PATH = join(repoRoot, CANONICAL_CONFIG_REL_PATH);
|
|
25
|
+
const LOCAL_OVERRIDE_PATH = join(
|
|
26
|
+
homedir(),
|
|
27
|
+
".vellum",
|
|
28
|
+
"chrome-extension-allowlist.local.json",
|
|
29
|
+
);
|
|
24
30
|
|
|
25
31
|
const EXTENSION_ID_REGEX = /^[a-p]{32}$/;
|
|
32
|
+
const PLACEHOLDER_ID_REGEX = /^TODO_[A-Z0-9_]+$/;
|
|
26
33
|
|
|
27
34
|
type AllowlistConfig = {
|
|
28
35
|
version: number;
|
|
@@ -48,19 +55,32 @@ function parseCanonicalConfig(): AllowlistConfig {
|
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
const seen = new Set<string>();
|
|
58
|
+
const validIds: string[] = [];
|
|
51
59
|
for (const id of parsed.allowedExtensionIds) {
|
|
52
|
-
if (typeof id !== "string"
|
|
60
|
+
if (typeof id !== "string") {
|
|
53
61
|
throw new Error(`Invalid canonical extension id: ${String(id)}`);
|
|
54
62
|
}
|
|
55
63
|
if (seen.has(id)) {
|
|
56
64
|
throw new Error(`Duplicate canonical extension id: ${id}`);
|
|
57
65
|
}
|
|
58
66
|
seen.add(id);
|
|
67
|
+
|
|
68
|
+
if (EXTENSION_ID_REGEX.test(id)) {
|
|
69
|
+
validIds.push(id);
|
|
70
|
+
} else if (!PLACEHOLDER_ID_REGEX.test(id)) {
|
|
71
|
+
throw new Error(`Invalid canonical extension id: ${id}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (validIds.length === 0) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"Invalid canonical config: allowedExtensionIds must contain at least one real extension id",
|
|
78
|
+
);
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
return {
|
|
62
82
|
version: parsed.version as number,
|
|
63
|
-
allowedExtensionIds:
|
|
83
|
+
allowedExtensionIds: validIds,
|
|
64
84
|
};
|
|
65
85
|
}
|
|
66
86
|
|
|
@@ -97,8 +117,18 @@ function listTextFilesRecursively(root: string): string[] {
|
|
|
97
117
|
const out: string[] = [];
|
|
98
118
|
|
|
99
119
|
function walk(dir: string): void {
|
|
100
|
-
|
|
120
|
+
let entries;
|
|
121
|
+
try {
|
|
122
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
123
|
+
} catch (err) {
|
|
124
|
+
// Directory may have been removed by a concurrent test; skip it.
|
|
125
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
for (const entry of entries) {
|
|
101
129
|
if (entry.name.startsWith(".DS_Store")) continue;
|
|
130
|
+
// Skip temp fixtures created by parallel tests (e.g. .test-starter-bundle-<pid>).
|
|
131
|
+
if (entry.name.startsWith(".test-")) continue;
|
|
102
132
|
const absPath = join(dir, entry.name);
|
|
103
133
|
if (entry.isDirectory()) {
|
|
104
134
|
if (ignoredDirs.has(entry.name)) continue;
|
|
@@ -111,7 +141,13 @@ function listTextFilesRecursively(root: string): string[] {
|
|
|
111
141
|
if (!allowedExtensions.has(ext)) continue;
|
|
112
142
|
|
|
113
143
|
// Skip large files to keep this guard lightweight.
|
|
114
|
-
|
|
144
|
+
let size: number;
|
|
145
|
+
try {
|
|
146
|
+
size = statSync(absPath).size;
|
|
147
|
+
} catch (err) {
|
|
148
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") continue;
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
115
151
|
if (size > 1_000_000) continue;
|
|
116
152
|
out.push(absPath);
|
|
117
153
|
}
|
|
@@ -128,12 +164,36 @@ describe("Chrome extension allowlist guard", () => {
|
|
|
128
164
|
expect(config.allowedExtensionIds.length).toBeGreaterThan(0);
|
|
129
165
|
});
|
|
130
166
|
|
|
131
|
-
test("assistant runtime allowlist
|
|
167
|
+
test("assistant runtime allowlist contains every canonical origin", () => {
|
|
168
|
+
// The runtime set is the union of canonical + local override
|
|
169
|
+
// (~/.vellum/chrome-extension-allowlist.local.json) + env var. A dev
|
|
170
|
+
// machine may have extras; we only assert the canonical IDs are present.
|
|
171
|
+
const config = parseCanonicalConfig();
|
|
172
|
+
for (const id of config.allowedExtensionIds) {
|
|
173
|
+
const origin = `chrome-extension://${id}/`;
|
|
174
|
+
expect(ALLOWED_EXTENSION_ORIGINS.has(origin)).toBe(true);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("assistant runtime allowlist exactly mirrors canonical when no override sources are active", () => {
|
|
179
|
+
// Exact-equality invariant: when neither the local override file nor
|
|
180
|
+
// the env-var override is active, the runtime set must equal the
|
|
181
|
+
// canonical set — nothing extra, nothing missing. A dev machine with
|
|
182
|
+
// an unpacked-extension override will skip this deterministic check
|
|
183
|
+
// and rely on the subset assertion above.
|
|
184
|
+
if (existsSync(LOCAL_OVERRIDE_PATH)) return;
|
|
185
|
+
if (
|
|
186
|
+
process.env.VELLUM_CHROME_EXTENSION_IDS ||
|
|
187
|
+
process.env.VELLUM_CHROME_EXTENSION_ID
|
|
188
|
+
) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
132
192
|
const config = parseCanonicalConfig();
|
|
133
193
|
const expectedOrigins = new Set(
|
|
134
194
|
config.allowedExtensionIds.map((id) => `chrome-extension://${id}/`),
|
|
135
195
|
);
|
|
136
|
-
expect(ALLOWED_EXTENSION_ORIGINS).toEqual(expectedOrigins);
|
|
196
|
+
expect(new Set(ALLOWED_EXTENSION_ORIGINS)).toEqual(expectedOrigins);
|
|
137
197
|
});
|
|
138
198
|
|
|
139
199
|
test("concrete extension IDs appear only in canonical config", () => {
|
|
@@ -144,7 +204,14 @@ describe("Chrome extension allowlist guard", () => {
|
|
|
144
204
|
const matches: string[] = [];
|
|
145
205
|
for (const absPath of allFiles) {
|
|
146
206
|
const relPath = absPath.replace(`${repoRoot}/`, "");
|
|
147
|
-
|
|
207
|
+
let content: string;
|
|
208
|
+
try {
|
|
209
|
+
content = readFileSync(absPath, "utf8");
|
|
210
|
+
} catch (err) {
|
|
211
|
+
// File may have been removed by a concurrent test between listing and reading.
|
|
212
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") continue;
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
148
215
|
if (content.includes(extensionId)) {
|
|
149
216
|
matches.push(relPath);
|
|
150
217
|
}
|
|
@@ -124,6 +124,12 @@ export interface MockChromeExtension {
|
|
|
124
124
|
* command.
|
|
125
125
|
*/
|
|
126
126
|
sendSessionInvalidated(event: { targetId?: string; reason?: string }): void;
|
|
127
|
+
/**
|
|
128
|
+
* Send an arbitrary pre-serialized JSON string over the active
|
|
129
|
+
* WebSocket. Used by tests that need to send frame types not covered
|
|
130
|
+
* by the fixture's typed helpers (e.g. keepalive frames).
|
|
131
|
+
*/
|
|
132
|
+
sendRaw(json: string): void;
|
|
127
133
|
}
|
|
128
134
|
|
|
129
135
|
// ── Defaults ────────────────────────────────────────────────────────
|
|
@@ -371,5 +377,10 @@ export function createMockChromeExtension(
|
|
|
371
377
|
}),
|
|
372
378
|
);
|
|
373
379
|
},
|
|
380
|
+
sendRaw(json: string) {
|
|
381
|
+
const sock = ws;
|
|
382
|
+
if (!sock || sock.readyState !== WebSocket.OPEN) return;
|
|
383
|
+
sock.send(json);
|
|
384
|
+
},
|
|
374
385
|
};
|
|
375
386
|
}
|
|
@@ -68,6 +68,9 @@ mock.module("../config/loader.js", () => ({
|
|
|
68
68
|
twilio: {
|
|
69
69
|
phoneNumber: "+15550001111",
|
|
70
70
|
},
|
|
71
|
+
services: {
|
|
72
|
+
stt: { provider: "deepgram" },
|
|
73
|
+
},
|
|
71
74
|
}),
|
|
72
75
|
invalidateConfigCache: () => {},
|
|
73
76
|
}));
|
|
@@ -90,7 +93,9 @@ mock.module("../calls/twilio-provider.js", () => ({
|
|
|
90
93
|
},
|
|
91
94
|
}));
|
|
92
95
|
|
|
93
|
-
mock.module("../security/secure-keys.js", () => ({
|
|
96
|
+
mock.module("../security/secure-keys.js", () => ({
|
|
97
|
+
getProviderKeyAsync: () => Promise.resolve(null),
|
|
98
|
+
}));
|
|
94
99
|
|
|
95
100
|
// NOTE: Do NOT mock '../inbound/public-ingress-urls.js' here.
|
|
96
101
|
// Those are pure functions that derive URLs from the config object returned by
|
|
@@ -455,6 +460,86 @@ describe("gateway-only ingress enforcement", () => {
|
|
|
455
460
|
});
|
|
456
461
|
});
|
|
457
462
|
|
|
463
|
+
// ── Media-stream WebSocket upgrade ─────────────────────────────────
|
|
464
|
+
|
|
465
|
+
describe("media-stream WebSocket upgrade", () => {
|
|
466
|
+
test("blocks non-private-network origin", async () => {
|
|
467
|
+
// The peer address (127.0.0.1) passes the private network check,
|
|
468
|
+
// but the external Origin header triggers the secondary defense-in-depth block.
|
|
469
|
+
const res = await fetch(
|
|
470
|
+
`http://127.0.0.1:${port}/v1/calls/media-stream?callSessionId=sess-123`,
|
|
471
|
+
{
|
|
472
|
+
headers: {
|
|
473
|
+
Upgrade: "websocket",
|
|
474
|
+
Connection: "Upgrade",
|
|
475
|
+
Origin: "https://external.example.com",
|
|
476
|
+
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
477
|
+
"Sec-WebSocket-Version": "13",
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
);
|
|
481
|
+
expect(res.status).toBe(403);
|
|
482
|
+
const body = (await res.json()) as {
|
|
483
|
+
error: { code: string; message: string };
|
|
484
|
+
};
|
|
485
|
+
expect(body.error.code).toBe("FORBIDDEN");
|
|
486
|
+
expect(body.error.message).toContain(
|
|
487
|
+
"Direct media-stream access disabled",
|
|
488
|
+
);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("allows request with no origin header (private network peer)", async () => {
|
|
492
|
+
// Without an origin header, isPrivateNetworkOrigin returns true.
|
|
493
|
+
// The peer address (127.0.0.1) passes the private network peer check.
|
|
494
|
+
const res = await fetch(
|
|
495
|
+
`http://127.0.0.1:${port}/v1/calls/media-stream?callSessionId=sess-123`,
|
|
496
|
+
{
|
|
497
|
+
headers: {
|
|
498
|
+
Upgrade: "websocket",
|
|
499
|
+
Connection: "Upgrade",
|
|
500
|
+
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
501
|
+
"Sec-WebSocket-Version": "13",
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
);
|
|
505
|
+
// Should NOT be 403 — WebSocket upgrade may or may not succeed
|
|
506
|
+
// depending on test environment, but the gateway guard should pass.
|
|
507
|
+
expect(res.status).not.toBe(403);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("allows localhost origin from loopback peer", async () => {
|
|
511
|
+
const res = await fetch(
|
|
512
|
+
`http://127.0.0.1:${port}/v1/calls/media-stream?callSessionId=sess-123`,
|
|
513
|
+
{
|
|
514
|
+
headers: {
|
|
515
|
+
Upgrade: "websocket",
|
|
516
|
+
Connection: "Upgrade",
|
|
517
|
+
Origin: "http://127.0.0.1:3000",
|
|
518
|
+
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
519
|
+
"Sec-WebSocket-Version": "13",
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
);
|
|
523
|
+
// Should NOT be 403
|
|
524
|
+
expect(res.status).not.toBe(403);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test("returns 400 when callSessionId is missing", async () => {
|
|
528
|
+
const res = await fetch(
|
|
529
|
+
`http://127.0.0.1:${port}/v1/calls/media-stream`,
|
|
530
|
+
{
|
|
531
|
+
headers: {
|
|
532
|
+
Upgrade: "websocket",
|
|
533
|
+
Connection: "Upgrade",
|
|
534
|
+
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
535
|
+
"Sec-WebSocket-Version": "13",
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
);
|
|
539
|
+
expect(res.status).toBe(400);
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
|
|
458
543
|
// ── isPrivateAddress unit tests ─────────────────────────────────────
|
|
459
544
|
|
|
460
545
|
describe("isPrivateAddress", () => {
|
|
@@ -686,6 +771,126 @@ describe("gateway-only ingress enforcement", () => {
|
|
|
686
771
|
});
|
|
687
772
|
});
|
|
688
773
|
|
|
774
|
+
// ── STT stream WebSocket upgrade ────────────────────────────────────
|
|
775
|
+
|
|
776
|
+
describe("STT stream WebSocket upgrade", () => {
|
|
777
|
+
test("blocks non-private-network origin", async () => {
|
|
778
|
+
const res = await fetch(
|
|
779
|
+
`http://127.0.0.1:${port}/v1/stt/stream?provider=deepgram&mimeType=audio/webm`,
|
|
780
|
+
{
|
|
781
|
+
headers: {
|
|
782
|
+
Upgrade: "websocket",
|
|
783
|
+
Connection: "Upgrade",
|
|
784
|
+
Origin: "https://external.example.com",
|
|
785
|
+
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
786
|
+
"Sec-WebSocket-Version": "13",
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
);
|
|
790
|
+
expect(res.status).toBe(403);
|
|
791
|
+
const body = (await res.json()) as {
|
|
792
|
+
error: { code: string; message: string };
|
|
793
|
+
};
|
|
794
|
+
expect(body.error.code).toBe("FORBIDDEN");
|
|
795
|
+
expect(body.error.message).toContain("Direct STT stream access disabled");
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
test("rejects upgrade without a token", async () => {
|
|
799
|
+
const res = await fetch(
|
|
800
|
+
`http://127.0.0.1:${port}/v1/stt/stream?provider=deepgram&mimeType=audio/webm`,
|
|
801
|
+
{
|
|
802
|
+
headers: {
|
|
803
|
+
Upgrade: "websocket",
|
|
804
|
+
Connection: "Upgrade",
|
|
805
|
+
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
806
|
+
"Sec-WebSocket-Version": "13",
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
);
|
|
810
|
+
expect(res.status).toBe(401);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
test("rejects upgrade with actor JWT (requires gateway service token)", async () => {
|
|
814
|
+
const res = await fetch(
|
|
815
|
+
`http://127.0.0.1:${port}/v1/stt/stream?token=${TEST_JWT}&provider=deepgram&mimeType=audio/webm`,
|
|
816
|
+
{
|
|
817
|
+
headers: {
|
|
818
|
+
Upgrade: "websocket",
|
|
819
|
+
Connection: "Upgrade",
|
|
820
|
+
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
821
|
+
"Sec-WebSocket-Version": "13",
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
);
|
|
825
|
+
// Actor JWTs should be rejected — only gateway service tokens are allowed.
|
|
826
|
+
expect(res.status).toBe(401);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test("accepts upgrade with gateway service token from private network", async () => {
|
|
830
|
+
const res = await fetch(
|
|
831
|
+
`http://127.0.0.1:${port}/v1/stt/stream?token=${GATEWAY_JWT}&provider=deepgram&mimeType=audio/webm`,
|
|
832
|
+
{
|
|
833
|
+
headers: {
|
|
834
|
+
Upgrade: "websocket",
|
|
835
|
+
Connection: "Upgrade",
|
|
836
|
+
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
837
|
+
"Sec-WebSocket-Version": "13",
|
|
838
|
+
},
|
|
839
|
+
},
|
|
840
|
+
);
|
|
841
|
+
// Should NOT be 403 or 401 — WebSocket upgrade may or may not succeed
|
|
842
|
+
// depending on test environment, but the auth and network guards should pass.
|
|
843
|
+
expect(res.status).not.toBe(403);
|
|
844
|
+
expect(res.status).not.toBe(401);
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
test("succeeds when provider is omitted (config-authoritative)", async () => {
|
|
848
|
+
const res = await fetch(
|
|
849
|
+
`http://127.0.0.1:${port}/v1/stt/stream?token=${GATEWAY_JWT}&mimeType=audio/webm`,
|
|
850
|
+
{
|
|
851
|
+
headers: {
|
|
852
|
+
Upgrade: "websocket",
|
|
853
|
+
Connection: "Upgrade",
|
|
854
|
+
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
855
|
+
"Sec-WebSocket-Version": "13",
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
);
|
|
859
|
+
// Should NOT be 400 — provider is optional.
|
|
860
|
+
expect(res.status).not.toBe(400);
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
test("returns 400 when mimeType is missing", async () => {
|
|
864
|
+
const res = await fetch(
|
|
865
|
+
`http://127.0.0.1:${port}/v1/stt/stream?token=${GATEWAY_JWT}&provider=deepgram`,
|
|
866
|
+
{
|
|
867
|
+
headers: {
|
|
868
|
+
Upgrade: "websocket",
|
|
869
|
+
Connection: "Upgrade",
|
|
870
|
+
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
871
|
+
"Sec-WebSocket-Version": "13",
|
|
872
|
+
},
|
|
873
|
+
},
|
|
874
|
+
);
|
|
875
|
+
expect(res.status).toBe(400);
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
test("returns 400 when both provider and mimeType are missing", async () => {
|
|
879
|
+
const res = await fetch(
|
|
880
|
+
`http://127.0.0.1:${port}/v1/stt/stream?token=${GATEWAY_JWT}`,
|
|
881
|
+
{
|
|
882
|
+
headers: {
|
|
883
|
+
Upgrade: "websocket",
|
|
884
|
+
Connection: "Upgrade",
|
|
885
|
+
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
886
|
+
"Sec-WebSocket-Version": "13",
|
|
887
|
+
},
|
|
888
|
+
},
|
|
889
|
+
);
|
|
890
|
+
expect(res.status).toBe(400);
|
|
891
|
+
});
|
|
892
|
+
});
|
|
893
|
+
|
|
689
894
|
// ── Startup warning for non-loopback host ──────────────────────────
|
|
690
895
|
|
|
691
896
|
describe("startup guard — non-loopback host", () => {
|
|
@@ -23,7 +23,6 @@ const ALLOWLIST = new Set([
|
|
|
23
23
|
|
|
24
24
|
// --- Intentional local daemon-control paths ---
|
|
25
25
|
"assistant/src/cli/commands/conversations.ts", // CLI wipe talks to runtime directly
|
|
26
|
-
"assistant/src/cli/commands/browser-relay.ts", // CLI shim talks to /v1/browser-cdp on the local daemon
|
|
27
26
|
"clients/shared/Network/DaemonClient.swift",
|
|
28
27
|
"clients/shared/App/Auth/PlatformOAuthService.swift", // comment explaining runtimeUrl vs platformUrl
|
|
29
28
|
"clients/macos/vellum-assistant/App/AppDelegate.swift",
|