@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
|
@@ -225,8 +225,7 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
|
|
|
225
225
|
// Same fixture as the HTTP happy path, but configured to return
|
|
226
226
|
// results over the /v1/browser-relay WebSocket instead of POSTing
|
|
227
227
|
// /v1/host-browser-result. This exercises the runtime WS
|
|
228
|
-
// `message` handler's host_browser_result dispatch path
|
|
229
|
-
// PR2 of the browser-use remediation plan.
|
|
228
|
+
// `message` handler's host_browser_result dispatch path.
|
|
230
229
|
const mockExt = createMockChromeExtension({
|
|
231
230
|
runtimeBaseUrl,
|
|
232
231
|
token,
|
|
@@ -309,8 +308,7 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
|
|
|
309
308
|
});
|
|
310
309
|
|
|
311
310
|
test("abort: late /v1/host-browser-result POST after cancel is ignored (no ghost completion)", async () => {
|
|
312
|
-
//
|
|
313
|
-
// daemon-side proxy must treat a late result POST — arriving
|
|
311
|
+
// The daemon-side proxy must treat a late result POST — arriving
|
|
314
312
|
// after the caller has already been resolved with "Aborted" —
|
|
315
313
|
// as a benign race, not a noisy false-positive timeout. It must
|
|
316
314
|
// also NOT resolve the caller a second time.
|
|
@@ -434,6 +432,142 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
|
|
|
434
432
|
});
|
|
435
433
|
});
|
|
436
434
|
|
|
435
|
+
// ── macOS message ingress with connected extension ──────────────────
|
|
436
|
+
//
|
|
437
|
+
// Verifies the end-to-end path for macOS-originated turns when the user
|
|
438
|
+
// has the chrome extension connected. On macOS, browser commands should
|
|
439
|
+
// route through the registry-backed host browser flow (extension → user's
|
|
440
|
+
// real Chrome session) rather than falling back to local Playwright.
|
|
441
|
+
//
|
|
442
|
+
// The macOS browser backend preference order is:
|
|
443
|
+
//
|
|
444
|
+
// macOS + extension connected → extension backend (registry-routed)
|
|
445
|
+
// macOS + extension absent → cdp-inspect (desktop-auto) → local
|
|
446
|
+
//
|
|
447
|
+
// NOTE: These tests construct a HostBrowserProxy directly and call
|
|
448
|
+
// proxy.request(), which validates the extension relay round-trip but
|
|
449
|
+
// bypasses handleSendMessage / conversation-routes. Full ingress-path
|
|
450
|
+
// coverage (interface propagation, resolveHostBrowserSender wiring, and
|
|
451
|
+
// CDP factory candidate selection) is exercised by the route-level tests
|
|
452
|
+
// in conversation-routes-disk-view.test.ts.
|
|
453
|
+
//
|
|
454
|
+
// If future refactors break the wiring between conversation-routes
|
|
455
|
+
// (`resolveHostBrowserSender`) and the CDP factory's candidate list, those
|
|
456
|
+
// route-level tests will fail.
|
|
457
|
+
|
|
458
|
+
describe("macOS message ingress with connected extension", () => {
|
|
459
|
+
let server: RuntimeHttpServer;
|
|
460
|
+
let port: number;
|
|
461
|
+
let runtimeBaseUrl: string;
|
|
462
|
+
|
|
463
|
+
beforeEach(async () => {
|
|
464
|
+
const db = getDb();
|
|
465
|
+
db.run("DELETE FROM contact_channels");
|
|
466
|
+
db.run("DELETE FROM contacts");
|
|
467
|
+
pendingInteractions.clear();
|
|
468
|
+
__resetChromeExtensionRegistryForTests();
|
|
469
|
+
|
|
470
|
+
port = 20000 + Math.floor(Math.random() * 200);
|
|
471
|
+
runtimeBaseUrl = `http://127.0.0.1:${port}`;
|
|
472
|
+
server = new RuntimeHttpServer({ port });
|
|
473
|
+
await server.start();
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
afterEach(async () => {
|
|
477
|
+
await server?.stop();
|
|
478
|
+
pendingInteractions.clear();
|
|
479
|
+
__resetChromeExtensionRegistryForTests();
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test("macOS turn routes Browser.getVersion through the registry-backed extension, not local Playwright", async () => {
|
|
483
|
+
// Arrange: connect a mock extension for a given guardianId.
|
|
484
|
+
const guardianId = `test-guardian-macos-${crypto.randomUUID()}`;
|
|
485
|
+
const token = mintActorToken(guardianId);
|
|
486
|
+
|
|
487
|
+
const { createMockChromeExtension } =
|
|
488
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
489
|
+
const mockExt = createMockChromeExtension({
|
|
490
|
+
runtimeBaseUrl,
|
|
491
|
+
token,
|
|
492
|
+
});
|
|
493
|
+
await mockExt.start();
|
|
494
|
+
await mockExt.waitForConnection();
|
|
495
|
+
await waitForRegistryEntry(guardianId);
|
|
496
|
+
|
|
497
|
+
// Build a proxy bound to the guardian's extension connection, mimicking
|
|
498
|
+
// the wiring that conversation-routes.ts performs for macOS turns when
|
|
499
|
+
// the ChromeExtensionRegistry has an active entry for the guardian.
|
|
500
|
+
const { proxy } = createBoundProxy(guardianId, "conv-macos-ext");
|
|
501
|
+
|
|
502
|
+
// Act: issue a CDP command through the proxy (same as how browser tools
|
|
503
|
+
// dispatch commands during a macOS turn with extension override).
|
|
504
|
+
const result = await proxy.request(
|
|
505
|
+
{ cdpMethod: "Browser.getVersion" },
|
|
506
|
+
"conv-macos-ext",
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
// Assert: the command reached the mock extension (not local Playwright)
|
|
510
|
+
// and the round-trip completed successfully.
|
|
511
|
+
expect(result.isError).toBe(false);
|
|
512
|
+
expect(result.content).toContain("Chrome/MockTest");
|
|
513
|
+
|
|
514
|
+
const received = mockExt.receivedRequests();
|
|
515
|
+
expect(received).toHaveLength(1);
|
|
516
|
+
expect(received[0].cdpMethod).toBe("Browser.getVersion");
|
|
517
|
+
expect(received[0].conversationId).toBe("conv-macos-ext");
|
|
518
|
+
|
|
519
|
+
proxy.dispose();
|
|
520
|
+
await mockExt.stop();
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
test("macOS turn with extension disconnected mid-conversation does not hang (proxy detects unavailability)", async () => {
|
|
524
|
+
// Arrange: connect a mock extension then forcibly disconnect it.
|
|
525
|
+
const guardianId = `test-guardian-macos-disco-${crypto.randomUUID()}`;
|
|
526
|
+
const token = mintActorToken(guardianId);
|
|
527
|
+
|
|
528
|
+
const { createMockChromeExtension } =
|
|
529
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
530
|
+
const mockExt = createMockChromeExtension({
|
|
531
|
+
runtimeBaseUrl,
|
|
532
|
+
token,
|
|
533
|
+
});
|
|
534
|
+
await mockExt.start();
|
|
535
|
+
await mockExt.waitForConnection();
|
|
536
|
+
await waitForRegistryEntry(guardianId);
|
|
537
|
+
|
|
538
|
+
// The proxy is bound while the extension is still connected.
|
|
539
|
+
const { proxy } = createBoundProxy(guardianId, "conv-macos-disco");
|
|
540
|
+
|
|
541
|
+
// Disconnect the extension before sending any commands.
|
|
542
|
+
mockExt.forceDisconnect();
|
|
543
|
+
|
|
544
|
+
// Wait for the registry to notice the close event.
|
|
545
|
+
await waitFor(
|
|
546
|
+
() => getChromeExtensionRegistry().get(guardianId) === undefined,
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
// Act: attempt a CDP command through the proxy. The registry send should
|
|
550
|
+
// fail because the connection is gone, and the proxy's sendToClient
|
|
551
|
+
// wrapper throws immediately.
|
|
552
|
+
try {
|
|
553
|
+
await proxy.request(
|
|
554
|
+
{ cdpMethod: "Browser.getVersion", timeout_seconds: 0.5 },
|
|
555
|
+
"conv-macos-disco",
|
|
556
|
+
);
|
|
557
|
+
// If we reach here, the test should still verify the result indicates
|
|
558
|
+
// an error rather than a successful extension round-trip.
|
|
559
|
+
expect(true).toBe(false); // Should not reach here
|
|
560
|
+
} catch {
|
|
561
|
+
// Expected: the send failed because the extension is disconnected.
|
|
562
|
+
// This confirms the macOS path detects disconnection rather than
|
|
563
|
+
// silently routing to the wrong backend.
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
proxy.dispose();
|
|
567
|
+
await mockExt.stop();
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
|
|
437
571
|
// ── Local wait helpers ──────────────────────────────────────────────
|
|
438
572
|
|
|
439
573
|
async function waitFor(
|
|
@@ -321,7 +321,7 @@ describe("host-browser E2E — self-hosted native messaging path", () => {
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
// -------------------------------------------------------------------------
|
|
324
|
-
// Dev-only
|
|
324
|
+
// Dev-only daemon-token fallback (per-instance protected dir)
|
|
325
325
|
// -------------------------------------------------------------------------
|
|
326
326
|
|
|
327
327
|
describe("dev daemon-token fallback path", () => {
|
|
@@ -338,9 +338,9 @@ describe("host-browser E2E — self-hosted native messaging path", () => {
|
|
|
338
338
|
test("a token written to a local file round-trips through verifyHostBrowserCapability", () => {
|
|
339
339
|
// Emulate the `writeDaemonTokenFallback` lifecycle: mint a fresh
|
|
340
340
|
// capability token, persist it to a 0600 file (the production
|
|
341
|
-
// helper writes to
|
|
342
|
-
// so the test doesn't clobber real dev state), then read
|
|
343
|
-
// and verify.
|
|
341
|
+
// helper writes to `<protectedDir>/daemon-token`, but we use a
|
|
342
|
+
// tempdir so the test doesn't clobber real dev state), then read
|
|
343
|
+
// it back and verify.
|
|
344
344
|
//
|
|
345
345
|
// This path is what the Mac app's manual "paste daemon token"
|
|
346
346
|
// pairing UI ends up exercising — the file on disk is the only
|
|
@@ -284,6 +284,109 @@ describe("host_browser WS event + invalidation e2e", () => {
|
|
|
284
284
|
await mockExt.stop();
|
|
285
285
|
});
|
|
286
286
|
|
|
287
|
+
test("keepalive frames are accepted without closing the socket or producing warnings", async () => {
|
|
288
|
+
const guardianId = `guardian-${crypto.randomUUID()}`;
|
|
289
|
+
const { token } = mintHostBrowserCapability(guardianId);
|
|
290
|
+
|
|
291
|
+
const { createMockChromeExtension } =
|
|
292
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
293
|
+
const mockExt = createMockChromeExtension({
|
|
294
|
+
runtimeBaseUrl,
|
|
295
|
+
token,
|
|
296
|
+
resultTransport: "ws",
|
|
297
|
+
});
|
|
298
|
+
await mockExt.start();
|
|
299
|
+
await mockExt.waitForConnection();
|
|
300
|
+
await waitForRegistryEntry(guardianId);
|
|
301
|
+
|
|
302
|
+
// Grab the initial timestamps so we can verify that keepalive
|
|
303
|
+
// bumps lastKeepaliveAt but NOT lastActiveAt (routing field).
|
|
304
|
+
const connBefore = getChromeExtensionRegistry().get(guardianId)!;
|
|
305
|
+
const lastActiveBefore = connBefore.lastActiveAt;
|
|
306
|
+
|
|
307
|
+
// Small delay to ensure Date.now() advances at least 1ms.
|
|
308
|
+
await new Promise((r) => setTimeout(r, 15));
|
|
309
|
+
|
|
310
|
+
// Send a keepalive frame (the extension sends these periodically
|
|
311
|
+
// to prevent the runtime from considering the connection stale).
|
|
312
|
+
// The frame may contain extra keys (e.g. timestamp) that the
|
|
313
|
+
// runtime should silently ignore (lenient validation).
|
|
314
|
+
mockExt.sendRaw(JSON.stringify({ type: "keepalive", ts: Date.now() }));
|
|
315
|
+
|
|
316
|
+
// Wait for the touch to propagate — touch() updates
|
|
317
|
+
// lastKeepaliveAt (not lastActiveAt) to avoid routing interference.
|
|
318
|
+
await waitFor(() => {
|
|
319
|
+
const conn = getChromeExtensionRegistry().get(guardianId);
|
|
320
|
+
return conn !== undefined && (conn.lastKeepaliveAt ?? 0) > 0;
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const connAfter = getChromeExtensionRegistry().get(guardianId)!;
|
|
324
|
+
expect(connAfter.lastKeepaliveAt).toBeGreaterThan(0);
|
|
325
|
+
// lastActiveAt must remain unchanged — keepalives must not affect routing.
|
|
326
|
+
expect(connAfter.lastActiveAt).toBe(lastActiveBefore);
|
|
327
|
+
|
|
328
|
+
// Verify the socket is still alive by sending a normal host_browser_event
|
|
329
|
+
// frame after the keepalive — if the socket had been torn down, this
|
|
330
|
+
// would never arrive.
|
|
331
|
+
const observed: ForwardedCdpEvent[] = [];
|
|
332
|
+
const unsubscribe = onCdpEvent((event) => observed.push(event));
|
|
333
|
+
|
|
334
|
+
mockExt.sendHostBrowserEvent({ method: "Page.loadEventFired" });
|
|
335
|
+
await waitFor(() => observed.length === 1);
|
|
336
|
+
expect(observed[0].method).toBe("Page.loadEventFired");
|
|
337
|
+
|
|
338
|
+
unsubscribe();
|
|
339
|
+
await mockExt.stop();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("normal host_browser flows still pass after keepalive traffic", async () => {
|
|
343
|
+
const guardianId = `guardian-${crypto.randomUUID()}`;
|
|
344
|
+
const { token } = mintHostBrowserCapability(guardianId);
|
|
345
|
+
|
|
346
|
+
const { createMockChromeExtension } =
|
|
347
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
348
|
+
const mockExt = createMockChromeExtension({
|
|
349
|
+
runtimeBaseUrl,
|
|
350
|
+
token,
|
|
351
|
+
resultTransport: "ws",
|
|
352
|
+
});
|
|
353
|
+
await mockExt.start();
|
|
354
|
+
await mockExt.waitForConnection();
|
|
355
|
+
await waitForRegistryEntry(guardianId);
|
|
356
|
+
|
|
357
|
+
// Simulate a burst of keepalive frames (as would happen during an
|
|
358
|
+
// idle period with the extension's alarm-based keepalive ticker).
|
|
359
|
+
for (let i = 0; i < 5; i++) {
|
|
360
|
+
mockExt.sendRaw(JSON.stringify({ type: "keepalive" }));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Small delay to let all keepalive frames process.
|
|
364
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
365
|
+
|
|
366
|
+
// Now send a host_browser_event and verify it still fans out
|
|
367
|
+
// correctly — proving keepalive traffic does not interfere with
|
|
368
|
+
// normal message processing.
|
|
369
|
+
const observed: ForwardedCdpEvent[] = [];
|
|
370
|
+
const unsubscribe = onCdpEvent((event) => observed.push(event));
|
|
371
|
+
|
|
372
|
+
mockExt.sendHostBrowserEvent({
|
|
373
|
+
method: "Network.requestWillBeSent",
|
|
374
|
+
params: { requestId: "req-42", url: "https://example.com/api" },
|
|
375
|
+
cdpSessionId: "session-xyz",
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
await waitFor(() => observed.length === 1);
|
|
379
|
+
expect(observed[0].method).toBe("Network.requestWillBeSent");
|
|
380
|
+
expect(observed[0].params).toEqual({
|
|
381
|
+
requestId: "req-42",
|
|
382
|
+
url: "https://example.com/api",
|
|
383
|
+
});
|
|
384
|
+
expect(observed[0].cdpSessionId).toBe("session-xyz");
|
|
385
|
+
|
|
386
|
+
unsubscribe();
|
|
387
|
+
await mockExt.stop();
|
|
388
|
+
});
|
|
389
|
+
|
|
287
390
|
test("malformed host_browser_event frames are dropped without tearing down the socket", async () => {
|
|
288
391
|
const guardianId = `guardian-${crypto.randomUUID()}`;
|
|
289
392
|
const { token } = mintHostBrowserCapability(guardianId);
|
|
@@ -792,26 +792,21 @@ describe("HostCuProxy", () => {
|
|
|
792
792
|
function spySignal(source: AbortSignal): Spied {
|
|
793
793
|
const addCalls: string[] = [];
|
|
794
794
|
const removeCalls: string[] = [];
|
|
795
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
796
795
|
const s = source as any;
|
|
797
796
|
const origAdd = source.addEventListener.bind(source);
|
|
798
797
|
const origRemove = source.removeEventListener.bind(source);
|
|
799
798
|
s.addEventListener = (
|
|
800
799
|
type: string,
|
|
801
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
802
800
|
...rest: any[]
|
|
803
801
|
) => {
|
|
804
802
|
addCalls.push(type);
|
|
805
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
806
803
|
return (origAdd as any)(type, ...rest);
|
|
807
804
|
};
|
|
808
805
|
s.removeEventListener = (
|
|
809
806
|
type: string,
|
|
810
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
811
807
|
...rest: any[]
|
|
812
808
|
) => {
|
|
813
809
|
removeCalls.push(type);
|
|
814
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
815
810
|
return (origRemove as any)(type, ...rest);
|
|
816
811
|
};
|
|
817
812
|
return { signal: source, addCalls, removeCalls };
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Unit tests for the identity intro cache (identity-intro-cache.ts).
|
|
3
3
|
*
|
|
4
4
|
* Validates TTL-based expiration, content-hash-based invalidation when
|
|
5
|
-
* workspace identity files change, and
|
|
5
|
+
* workspace identity files or the guardian persona content change, and
|
|
6
|
+
* round-trip get/set behavior.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -44,6 +45,14 @@ mock.module("node:fs", () => ({
|
|
|
44
45
|
},
|
|
45
46
|
}));
|
|
46
47
|
|
|
48
|
+
// Mocked guardian persona — mutable so tests can change it and verify cache
|
|
49
|
+
// invalidation based on the per-user persona file content.
|
|
50
|
+
let guardianPersonaContent: string | null = null;
|
|
51
|
+
|
|
52
|
+
mock.module("../prompts/persona-resolver.js", () => ({
|
|
53
|
+
resolveGuardianPersona: () => guardianPersonaContent,
|
|
54
|
+
}));
|
|
55
|
+
|
|
47
56
|
// ---------------------------------------------------------------------------
|
|
48
57
|
// Imports (after mocks)
|
|
49
58
|
// ---------------------------------------------------------------------------
|
|
@@ -63,6 +72,7 @@ afterEach(() => {
|
|
|
63
72
|
for (const key of Object.keys(workspaceFiles)) {
|
|
64
73
|
delete workspaceFiles[key];
|
|
65
74
|
}
|
|
75
|
+
guardianPersonaContent = null;
|
|
66
76
|
});
|
|
67
77
|
|
|
68
78
|
// ---------------------------------------------------------------------------
|
|
@@ -77,7 +87,7 @@ describe("identity intro cache", () => {
|
|
|
77
87
|
test("round-trip: set then get returns cached text", () => {
|
|
78
88
|
workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
|
|
79
89
|
workspaceFiles["SOUL.md"] = "Be playful.";
|
|
80
|
-
|
|
90
|
+
guardianPersonaContent = "The user likes coffee.";
|
|
81
91
|
|
|
82
92
|
setCachedIntro("Hey, I'm Atlas.");
|
|
83
93
|
const cached = getCachedIntro();
|
|
@@ -131,24 +141,24 @@ describe("identity intro cache", () => {
|
|
|
131
141
|
expect(getCachedIntro()).toBeNull();
|
|
132
142
|
});
|
|
133
143
|
|
|
134
|
-
test("busts cache when
|
|
135
|
-
|
|
144
|
+
test("busts cache when guardian persona content changes", () => {
|
|
145
|
+
guardianPersonaContent = "Likes coffee.";
|
|
136
146
|
setCachedIntro("Good morning!");
|
|
137
147
|
|
|
138
|
-
// Change
|
|
139
|
-
|
|
148
|
+
// Change guardian persona (e.g. user edited users/<slug>.md)
|
|
149
|
+
guardianPersonaContent = "Likes tea.";
|
|
140
150
|
|
|
141
151
|
expect(getCachedIntro()).toBeNull();
|
|
142
152
|
});
|
|
143
153
|
|
|
144
|
-
test("cache
|
|
154
|
+
test("cache remains valid when guardian persona is unchanged", () => {
|
|
145
155
|
workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
|
|
146
156
|
workspaceFiles["SOUL.md"] = "Be chill.";
|
|
147
|
-
|
|
157
|
+
guardianPersonaContent = "Likes sunsets.";
|
|
148
158
|
|
|
149
159
|
setCachedIntro("Atlas here.");
|
|
150
160
|
|
|
151
|
-
// Read twice — both should return the cached value
|
|
161
|
+
// Read twice with the same guardian persona — both should return the cached value
|
|
152
162
|
expect(getCachedIntro()?.text).toBe("Atlas here.");
|
|
153
163
|
expect(getCachedIntro()?.text).toBe("Atlas here.");
|
|
154
164
|
});
|
|
@@ -156,7 +166,7 @@ describe("identity intro cache", () => {
|
|
|
156
166
|
test("computeIdentityContentHash is deterministic", () => {
|
|
157
167
|
workspaceFiles["IDENTITY.md"] = "test";
|
|
158
168
|
workspaceFiles["SOUL.md"] = "test2";
|
|
159
|
-
|
|
169
|
+
guardianPersonaContent = "test3";
|
|
160
170
|
|
|
161
171
|
const hash1 = computeIdentityContentHash();
|
|
162
172
|
const hash2 = computeIdentityContentHash();
|
|
@@ -164,6 +174,26 @@ describe("identity intro cache", () => {
|
|
|
164
174
|
expect(hash1).toMatch(/^[a-f0-9]{64}$/); // SHA-256 hex
|
|
165
175
|
});
|
|
166
176
|
|
|
177
|
+
test("computeIdentityContentHash changes when guardian persona changes", () => {
|
|
178
|
+
workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
|
|
179
|
+
workspaceFiles["SOUL.md"] = "Be playful.";
|
|
180
|
+
guardianPersonaContent = "Likes coffee.";
|
|
181
|
+
const hash1 = computeIdentityContentHash();
|
|
182
|
+
|
|
183
|
+
guardianPersonaContent = "Likes tea.";
|
|
184
|
+
const hash2 = computeIdentityContentHash();
|
|
185
|
+
|
|
186
|
+
expect(hash1).not.toBe(hash2);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("computeIdentityContentHash handles null guardian persona", () => {
|
|
190
|
+
workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
|
|
191
|
+
guardianPersonaContent = null;
|
|
192
|
+
|
|
193
|
+
const hash = computeIdentityContentHash();
|
|
194
|
+
expect(hash).toMatch(/^[a-f0-9]{64}$/);
|
|
195
|
+
});
|
|
196
|
+
|
|
167
197
|
test("computeIdentityContentHash changes when file content changes", () => {
|
|
168
198
|
workspaceFiles["IDENTITY.md"] = "v1";
|
|
169
199
|
const hash1 = computeIdentityContentHash();
|
|
@@ -1,102 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for initFeatureFlagOverrides() — the async
|
|
2
|
+
* Tests for initFeatureFlagOverrides() — the async IPC call that
|
|
3
3
|
* pre-populates the feature flag cache before CLI program construction.
|
|
4
|
+
*
|
|
5
|
+
* Uses the shared mock-gateway-ipc utility (installed in test-preload.ts)
|
|
6
|
+
* which mocks node:net so no test connects to a real gateway socket.
|
|
4
7
|
*/
|
|
5
8
|
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
6
9
|
|
|
10
|
+
import {
|
|
11
|
+
mockGatewayIpc,
|
|
12
|
+
resetMockGatewayIpc,
|
|
13
|
+
} from "../__tests__/mock-gateway-ipc.js";
|
|
7
14
|
import {
|
|
8
15
|
clearFeatureFlagOverridesCache,
|
|
9
16
|
initFeatureFlagOverrides,
|
|
10
17
|
isAssistantFeatureFlagEnabled,
|
|
11
18
|
} from "../config/assistant-feature-flags.js";
|
|
12
|
-
import * as tokenService from "../runtime/auth/token-service.js";
|
|
13
|
-
import { getMockFetchCalls, mockFetch, resetMockFetch } from "./mock-fetch.js";
|
|
14
|
-
|
|
15
|
-
const VALID_HEX_KEY = "ab".repeat(32);
|
|
16
19
|
|
|
17
20
|
beforeEach(() => {
|
|
18
21
|
clearFeatureFlagOverridesCache();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// Set up a signing key so mintEdgeRelayToken() works
|
|
22
|
-
process.env.ACTOR_TOKEN_SIGNING_KEY = VALID_HEX_KEY;
|
|
23
|
-
tokenService.initAuthSigningKey(tokenService.resolveSigningKey());
|
|
22
|
+
resetMockGatewayIpc();
|
|
24
23
|
});
|
|
25
24
|
|
|
26
25
|
afterEach(() => {
|
|
27
|
-
resetMockFetch();
|
|
28
26
|
clearFeatureFlagOverridesCache();
|
|
29
|
-
|
|
30
|
-
delete process.env.ACTOR_TOKEN_SIGNING_KEY;
|
|
27
|
+
resetMockGatewayIpc();
|
|
31
28
|
});
|
|
32
29
|
|
|
33
30
|
describe("initFeatureFlagOverrides", () => {
|
|
34
|
-
it("populates cache from gateway
|
|
35
|
-
|
|
36
|
-
"/v1/feature-flags",
|
|
37
|
-
{ method: "GET" },
|
|
38
|
-
{
|
|
39
|
-
body: {
|
|
40
|
-
flags: [
|
|
41
|
-
{
|
|
42
|
-
key: "foo-enabled",
|
|
43
|
-
enabled: true,
|
|
44
|
-
label: "Foo",
|
|
45
|
-
defaultEnabled: false,
|
|
46
|
-
description: "",
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
key: "bar-enabled",
|
|
50
|
-
enabled: true,
|
|
51
|
-
label: "Bar",
|
|
52
|
-
defaultEnabled: true,
|
|
53
|
-
description: "",
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
},
|
|
57
|
-
status: 200,
|
|
58
|
-
},
|
|
59
|
-
);
|
|
31
|
+
it("populates cache from gateway IPC response", async () => {
|
|
32
|
+
mockGatewayIpc({ "foo-enabled": true, "bar-enabled": true });
|
|
60
33
|
|
|
61
34
|
await initFeatureFlagOverrides();
|
|
62
35
|
|
|
63
36
|
const config = {} as any;
|
|
64
37
|
expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(true);
|
|
65
38
|
expect(isAssistantFeatureFlagEnabled("bar-enabled", config)).toBe(true);
|
|
66
|
-
|
|
67
|
-
// Verify fetch was called with correct URL and auth header
|
|
68
|
-
const calls = getMockFetchCalls();
|
|
69
|
-
expect(calls.length).toBe(1);
|
|
70
|
-
expect(calls[0].path).toContain("/v1/feature-flags");
|
|
71
|
-
const headers = calls[0].init.headers as Record<string, string> | undefined;
|
|
72
|
-
expect(headers).toHaveProperty("Authorization");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("sends a valid Bearer JWT in the Authorization header", async () => {
|
|
76
|
-
mockFetch(
|
|
77
|
-
"/v1/feature-flags",
|
|
78
|
-
{ method: "GET" },
|
|
79
|
-
{ body: { flags: [] }, status: 200 },
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
await initFeatureFlagOverrides();
|
|
83
|
-
|
|
84
|
-
const calls = getMockFetchCalls();
|
|
85
|
-
expect(calls.length).toBe(1);
|
|
86
|
-
const headers = calls[0].init.headers as Record<string, string> | undefined;
|
|
87
|
-
const authHeader = headers?.Authorization;
|
|
88
|
-
|
|
89
|
-
expect(authHeader).toBeDefined();
|
|
90
|
-
expect(authHeader).toMatch(/^Bearer /);
|
|
91
|
-
|
|
92
|
-
// Verify it's a valid JWT (three dot-separated base64url segments)
|
|
93
|
-
const token = authHeader!.replace("Bearer ", "");
|
|
94
|
-
const parts = token.split(".");
|
|
95
|
-
expect(parts.length).toBe(3);
|
|
96
39
|
});
|
|
97
40
|
|
|
98
|
-
it("falls back gracefully when gateway is
|
|
99
|
-
|
|
41
|
+
it("falls back gracefully when gateway socket is unavailable", async () => {
|
|
42
|
+
mockGatewayIpc(null, { error: true });
|
|
100
43
|
|
|
101
44
|
// Should not throw
|
|
102
45
|
await initFeatureFlagOverrides();
|
|
@@ -106,62 +49,45 @@ describe("initFeatureFlagOverrides", () => {
|
|
|
106
49
|
expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(true);
|
|
107
50
|
});
|
|
108
51
|
|
|
109
|
-
it("
|
|
110
|
-
|
|
111
|
-
"/v1/feature-flags",
|
|
112
|
-
{ method: "GET" },
|
|
113
|
-
{ body: "Unauthorized", status: 401 },
|
|
114
|
-
);
|
|
52
|
+
it("respects false values from gateway IPC", async () => {
|
|
53
|
+
mockGatewayIpc({ "gated-feature": true, "disabled-feature": false });
|
|
115
54
|
|
|
116
55
|
await initFeatureFlagOverrides();
|
|
117
56
|
|
|
118
|
-
// Undeclared flags default to true without overrides
|
|
119
57
|
const config = {} as any;
|
|
120
|
-
expect(isAssistantFeatureFlagEnabled("
|
|
58
|
+
expect(isAssistantFeatureFlagEnabled("gated-feature", config)).toBe(true);
|
|
59
|
+
expect(isAssistantFeatureFlagEnabled("disabled-feature", config)).toBe(
|
|
60
|
+
false,
|
|
61
|
+
);
|
|
121
62
|
});
|
|
122
63
|
|
|
123
|
-
it("
|
|
124
|
-
|
|
125
|
-
tokenService._resetSigningKeyForTesting();
|
|
126
|
-
delete process.env.ACTOR_TOKEN_SIGNING_KEY;
|
|
127
|
-
|
|
128
|
-
expect(tokenService.isSigningKeyInitialized()).toBe(false);
|
|
129
|
-
|
|
130
|
-
mockFetch(
|
|
131
|
-
"/v1/feature-flags",
|
|
132
|
-
{ method: "GET" },
|
|
133
|
-
{
|
|
134
|
-
body: {
|
|
135
|
-
flags: [{ key: "expected-enabled", enabled: true }],
|
|
136
|
-
},
|
|
137
|
-
status: 200,
|
|
138
|
-
},
|
|
139
|
-
);
|
|
64
|
+
it("does not cache empty gateway response", async () => {
|
|
65
|
+
mockGatewayIpc({});
|
|
140
66
|
|
|
141
67
|
await initFeatureFlagOverrides();
|
|
142
68
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// And the flag should be resolved correctly
|
|
69
|
+
// Undeclared flags without overrides default to true (not false from
|
|
70
|
+
// a cached empty map)
|
|
147
71
|
const config = {} as any;
|
|
148
|
-
expect(isAssistantFeatureFlagEnabled("
|
|
149
|
-
true,
|
|
150
|
-
);
|
|
72
|
+
expect(isAssistantFeatureFlagEnabled("foo-enabled", config)).toBe(true);
|
|
151
73
|
});
|
|
152
74
|
|
|
153
|
-
it("does not cache
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
75
|
+
it("does not re-fetch when cache is already populated", async () => {
|
|
76
|
+
mockGatewayIpc({ "first-call": true });
|
|
77
|
+
|
|
78
|
+
await initFeatureFlagOverrides();
|
|
79
|
+
|
|
80
|
+
// Change what IPC would return — if the guard is broken and init
|
|
81
|
+
// re-fetches, "first-call" would flip to false.
|
|
82
|
+
resetMockGatewayIpc();
|
|
83
|
+
mockGatewayIpc({ "first-call": false, "second-call": true });
|
|
159
84
|
|
|
160
85
|
await initFeatureFlagOverrides();
|
|
161
86
|
|
|
162
|
-
// Undeclared flags without overrides default to true (not false from
|
|
163
|
-
// a cached empty map)
|
|
164
87
|
const config = {} as any;
|
|
165
|
-
|
|
88
|
+
// first-call must still be true (from the cached first fetch)
|
|
89
|
+
expect(isAssistantFeatureFlagEnabled("first-call", config)).toBe(true);
|
|
90
|
+
// second-call should not be in the cache since init was a no-op
|
|
91
|
+
expect(isAssistantFeatureFlagEnabled("second-call", config)).toBe(true); // defaults to true (undeclared)
|
|
166
92
|
});
|
|
167
93
|
});
|