@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,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the `_action: "launch_conversation"` dispatch branch in
|
|
3
|
+
* `handleSurfaceAction` AND the `launchConversation` helper it calls into.
|
|
4
|
+
*
|
|
5
|
+
* The real `launchConversation` is exercised end-to-end: its DB-hitting
|
|
6
|
+
* dependencies (`conversation-key-store`, `conversation-crud`) and its
|
|
7
|
+
* registered daemon deps are stubbed so the helper runs without a DB.
|
|
8
|
+
* This lets the tests assert the full invariant set in one place:
|
|
9
|
+
*
|
|
10
|
+
* - `handleSurfaceAction` does NOT publish `open_conversation` itself —
|
|
11
|
+
* `launchConversation` is the sole emitter of that event.
|
|
12
|
+
* - Exactly one `open_conversation` is published per launch, carrying
|
|
13
|
+
* the caller-supplied `focus` value (false for fan-out launchers).
|
|
14
|
+
* - The handler returns promptly — the seed turn
|
|
15
|
+
* (`persistAndProcessMessage`) is fire-and-forget.
|
|
16
|
+
* - `originTrustContext` is forwarded to the spawned conversation.
|
|
17
|
+
*
|
|
18
|
+
* These tests guard the single-emitter invariant: exactly one
|
|
19
|
+
* `open_conversation` event is published per launch, with the
|
|
20
|
+
* caller-supplied `focus` value preserved so fan-out launchers do not
|
|
21
|
+
* steal focus from the origin.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
25
|
+
|
|
26
|
+
// ── Module-level mocks ─────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
// Hub publish capture — used by the single-emit assertions. We spread
|
|
29
|
+
// the real module into each override so unrelated exports (e.g.
|
|
30
|
+
// `formatSseFrame` on assistant-event) stay accessible to other
|
|
31
|
+
// importers loaded indirectly through `conversation-surfaces.ts`.
|
|
32
|
+
const publishCalls: Array<unknown> = [];
|
|
33
|
+
const realHub = await import("../../runtime/assistant-event-hub.js");
|
|
34
|
+
const realEvent = await import("../../runtime/assistant-event.js");
|
|
35
|
+
mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
36
|
+
...realHub,
|
|
37
|
+
assistantEventHub: {
|
|
38
|
+
publish: async (event: unknown) => {
|
|
39
|
+
publishCalls.push(event);
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
mock.module("../../runtime/assistant-event.js", () => ({
|
|
44
|
+
...realEvent,
|
|
45
|
+
// Pass-through so `focus` / `conversationId` can be asserted directly
|
|
46
|
+
// on the captured event's `message` payload.
|
|
47
|
+
buildAssistantEvent: (
|
|
48
|
+
assistantId: string,
|
|
49
|
+
message: unknown,
|
|
50
|
+
conversationId?: string,
|
|
51
|
+
) => ({ assistantId, message, conversationId }),
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
// Stub DB helpers so the real `launchConversation` can run without a DB.
|
|
55
|
+
// We spread the real module and override only the specific functions the
|
|
56
|
+
// helper uses — other importers (e.g. conversation-surfaces itself)
|
|
57
|
+
// continue to see `getMessages`, `updateMessageContent`, etc.
|
|
58
|
+
let nextKeyStoreResult: { conversationId: string } = {
|
|
59
|
+
conversationId: "conv-new",
|
|
60
|
+
};
|
|
61
|
+
const updateTitleCalls: Array<{ conversationId: string; title: string }> = [];
|
|
62
|
+
const realKeyStore = await import("../../memory/conversation-key-store.js");
|
|
63
|
+
const realCrud = await import("../../memory/conversation-crud.js");
|
|
64
|
+
mock.module("../../memory/conversation-key-store.js", () => ({
|
|
65
|
+
...realKeyStore,
|
|
66
|
+
getOrCreateConversation: (_key: string) => nextKeyStoreResult,
|
|
67
|
+
}));
|
|
68
|
+
mock.module("../../memory/conversation-crud.js", () => ({
|
|
69
|
+
...realCrud,
|
|
70
|
+
updateConversationTitle: (
|
|
71
|
+
conversationId: string,
|
|
72
|
+
title: string,
|
|
73
|
+
_priority: number,
|
|
74
|
+
) => {
|
|
75
|
+
updateTitleCalls.push({ conversationId, title });
|
|
76
|
+
},
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
// Dynamic imports after mock.module calls so the stubs take effect
|
|
80
|
+
// before the modules under test are loaded.
|
|
81
|
+
const { createSurfaceMutex, handleSurfaceAction } =
|
|
82
|
+
await import("../conversation-surfaces.js");
|
|
83
|
+
const { registerLaunchConversationDeps, resetLaunchConversationDeps } =
|
|
84
|
+
await import("../conversation-launch.js");
|
|
85
|
+
type SurfaceConversationContext =
|
|
86
|
+
import("../conversation-surfaces.js").SurfaceConversationContext;
|
|
87
|
+
type TrustContext = import("../conversation-runtime-assembly.js").TrustContext;
|
|
88
|
+
type ServerMessage = import("../message-protocol.js").ServerMessage;
|
|
89
|
+
type SurfaceData = import("../message-protocol.js").SurfaceData;
|
|
90
|
+
type SurfaceType = import("../message-protocol.js").SurfaceType;
|
|
91
|
+
|
|
92
|
+
// ── launchConversation deps harness ────────────────────────────────
|
|
93
|
+
|
|
94
|
+
interface DepsHarness {
|
|
95
|
+
getOrCreateCalls: Array<string>;
|
|
96
|
+
processCalls: Array<{ conversationId: string; content: string }>;
|
|
97
|
+
lastTrustContext(): unknown | null;
|
|
98
|
+
resolveProcess: () => void;
|
|
99
|
+
rejectProcess: (err: Error) => void;
|
|
100
|
+
/** Resolves once `persistAndProcessMessage` has actually been invoked. */
|
|
101
|
+
processStarted: Promise<void>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function setupLaunchDeps(): DepsHarness {
|
|
105
|
+
const getOrCreateCalls: Array<string> = [];
|
|
106
|
+
const processCalls: Array<{ conversationId: string; content: string }> = [];
|
|
107
|
+
let trustContextOnConversation: unknown | null = null;
|
|
108
|
+
let resolveProcess = () => {};
|
|
109
|
+
let rejectProcess: (err: Error) => void = () => {};
|
|
110
|
+
let markProcessStarted = () => {};
|
|
111
|
+
const processStarted = new Promise<void>((resolve) => {
|
|
112
|
+
markProcessStarted = resolve;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const fakeConversation = {
|
|
116
|
+
setTrustContext: (c: unknown) => {
|
|
117
|
+
trustContextOnConversation = c;
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
registerLaunchConversationDeps({
|
|
122
|
+
getOrCreateConversation: async (id: string) => {
|
|
123
|
+
getOrCreateCalls.push(id);
|
|
124
|
+
return fakeConversation as never;
|
|
125
|
+
},
|
|
126
|
+
persistAndProcessMessage: (conversationId: string, content: string) => {
|
|
127
|
+
processCalls.push({ conversationId, content });
|
|
128
|
+
markProcessStarted();
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
resolveProcess = () => resolve({ messageId: "msg-1" });
|
|
131
|
+
rejectProcess = (err) => reject(err);
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
publishAssistantEvent: () => {},
|
|
135
|
+
getAssistantId: () => "assistant-test-id",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
getOrCreateCalls,
|
|
140
|
+
processCalls,
|
|
141
|
+
lastTrustContext: () => trustContextOnConversation,
|
|
142
|
+
resolveProcess: () => resolveProcess(),
|
|
143
|
+
rejectProcess: (err: Error) => rejectProcess(err),
|
|
144
|
+
processStarted,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Surface-context harness ────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
interface HarnessContext extends SurfaceConversationContext {
|
|
151
|
+
sent: ServerMessage[];
|
|
152
|
+
enqueueCalls: Array<{ content: string }>;
|
|
153
|
+
processCalls: Array<{ content: string }>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function makeContext(
|
|
157
|
+
overrides?: Partial<SurfaceConversationContext>,
|
|
158
|
+
): HarnessContext {
|
|
159
|
+
const sent: ServerMessage[] = [];
|
|
160
|
+
const enqueueCalls: Array<{ content: string }> = [];
|
|
161
|
+
const processCalls: Array<{ content: string }> = [];
|
|
162
|
+
|
|
163
|
+
const base: SurfaceConversationContext = {
|
|
164
|
+
conversationId: "origin-conv-id",
|
|
165
|
+
traceEmitter: { emit: () => {} },
|
|
166
|
+
sendToClient: (msg) => sent.push(msg),
|
|
167
|
+
pendingSurfaceActions: new Map<string, { surfaceType: SurfaceType }>(),
|
|
168
|
+
lastSurfaceAction: new Map<
|
|
169
|
+
string,
|
|
170
|
+
{ actionId: string; data?: Record<string, unknown> }
|
|
171
|
+
>(),
|
|
172
|
+
surfaceState: new Map<
|
|
173
|
+
string,
|
|
174
|
+
{ surfaceType: SurfaceType; data: SurfaceData; title?: string }
|
|
175
|
+
>(),
|
|
176
|
+
surfaceUndoStacks: new Map<string, string[]>(),
|
|
177
|
+
accumulatedSurfaceState: new Map<string, Record<string, unknown>>(),
|
|
178
|
+
surfaceActionRequestIds: new Set<string>(),
|
|
179
|
+
currentTurnSurfaces: [],
|
|
180
|
+
isProcessing: () => false,
|
|
181
|
+
enqueueMessage: (content: string) => {
|
|
182
|
+
enqueueCalls.push({ content });
|
|
183
|
+
return { queued: false, requestId: "enq-req" };
|
|
184
|
+
},
|
|
185
|
+
getQueueDepth: () => 0,
|
|
186
|
+
processMessage: async (content: string) => {
|
|
187
|
+
processCalls.push({ content });
|
|
188
|
+
return "ok";
|
|
189
|
+
},
|
|
190
|
+
withSurface: createSurfaceMutex(),
|
|
191
|
+
...overrides,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return Object.assign(base, {
|
|
195
|
+
sent,
|
|
196
|
+
enqueueCalls,
|
|
197
|
+
processCalls,
|
|
198
|
+
}) as HarnessContext;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Register a surface on `ctx`. Launcher cards arrive as history-restored
|
|
203
|
+
* surfaces (no `pendingSurfaceActions` entry) — matching how the card
|
|
204
|
+
* actually reaches `handleSurfaceAction` after reconstruction.
|
|
205
|
+
*/
|
|
206
|
+
function registerCardSurface(
|
|
207
|
+
ctx: SurfaceConversationContext,
|
|
208
|
+
surfaceId: string,
|
|
209
|
+
): void {
|
|
210
|
+
ctx.surfaceState.set(surfaceId, {
|
|
211
|
+
surfaceType: "card",
|
|
212
|
+
data: { title: "Launch" } as unknown as SurfaceData,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Helper: filter captured publish calls down to `open_conversation`
|
|
217
|
+
// events. Typed so assertions can reach the inner `message` payload.
|
|
218
|
+
function openConversationEvents(): Array<{
|
|
219
|
+
assistantId: string;
|
|
220
|
+
conversationId?: string;
|
|
221
|
+
message: {
|
|
222
|
+
type: "open_conversation";
|
|
223
|
+
conversationId: string;
|
|
224
|
+
title?: string;
|
|
225
|
+
anchorMessageId?: string;
|
|
226
|
+
focus?: boolean;
|
|
227
|
+
};
|
|
228
|
+
}> {
|
|
229
|
+
return publishCalls
|
|
230
|
+
.filter((e): e is { message: { type: "open_conversation" } } => {
|
|
231
|
+
const ev = e as { message?: { type?: string } };
|
|
232
|
+
return ev.message?.type === "open_conversation";
|
|
233
|
+
})
|
|
234
|
+
.map(
|
|
235
|
+
(e) =>
|
|
236
|
+
e as unknown as {
|
|
237
|
+
assistantId: string;
|
|
238
|
+
conversationId?: string;
|
|
239
|
+
message: {
|
|
240
|
+
type: "open_conversation";
|
|
241
|
+
conversationId: string;
|
|
242
|
+
title?: string;
|
|
243
|
+
anchorMessageId?: string;
|
|
244
|
+
focus?: boolean;
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Tests ──────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
describe("handleSurfaceAction — launch_conversation dispatch", () => {
|
|
253
|
+
beforeEach(() => {
|
|
254
|
+
publishCalls.length = 0;
|
|
255
|
+
updateTitleCalls.length = 0;
|
|
256
|
+
nextKeyStoreResult = { conversationId: "conv-new" };
|
|
257
|
+
// Reset module-level `_deps` so a test that forgets to call
|
|
258
|
+
// `setupLaunchDeps()` cannot accidentally piggy-back on deps left
|
|
259
|
+
// registered by a previous test. Each test that exercises the launch
|
|
260
|
+
// helper must call `setupLaunchDeps()` explicitly.
|
|
261
|
+
resetLaunchConversationDeps();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("launches new conversation with inherited trust context and no chat message", async () => {
|
|
265
|
+
nextKeyStoreResult = { conversationId: "conv-launched-1" };
|
|
266
|
+
const harness = setupLaunchDeps();
|
|
267
|
+
const originTrustContext: TrustContext = {
|
|
268
|
+
sourceChannel: "vellum",
|
|
269
|
+
trustClass: "guardian",
|
|
270
|
+
guardianChatId: "chat-guardian",
|
|
271
|
+
guardianPrincipalId: "principal-guardian",
|
|
272
|
+
};
|
|
273
|
+
const ctx = makeContext({ trustContext: originTrustContext });
|
|
274
|
+
registerCardSurface(ctx, "surface-1");
|
|
275
|
+
|
|
276
|
+
const result = await handleSurfaceAction(ctx, "surface-1", "launch", {
|
|
277
|
+
_action: "launch_conversation",
|
|
278
|
+
title: "New Thread",
|
|
279
|
+
seedPrompt: "S",
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// 1. Response shape.
|
|
283
|
+
expect(result).toEqual({
|
|
284
|
+
accepted: true,
|
|
285
|
+
conversationId: "conv-launched-1",
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// 2. Exactly ONE `open_conversation` event was published for the new
|
|
289
|
+
// id, with focus: false. `launchConversation` is the sole emitter;
|
|
290
|
+
// `handleSurfaceAction` delegates entirely to it.
|
|
291
|
+
const openEvents = openConversationEvents();
|
|
292
|
+
expect(openEvents).toHaveLength(1);
|
|
293
|
+
expect(openEvents[0].message.conversationId).toBe("conv-launched-1");
|
|
294
|
+
expect(openEvents[0].message.focus).toBe(false);
|
|
295
|
+
expect(openEvents[0].message.title).toBe("New Thread");
|
|
296
|
+
|
|
297
|
+
// 3. The spawned conversation inherited the origin's trust context.
|
|
298
|
+
expect(harness.lastTrustContext()).toEqual(originTrustContext);
|
|
299
|
+
|
|
300
|
+
// 4. Seed turn was kicked off fire-and-forget — resolve it to clean
|
|
301
|
+
// up the pending promise the harness stubbed.
|
|
302
|
+
await harness.processStarted;
|
|
303
|
+
expect(harness.processCalls).toHaveLength(1);
|
|
304
|
+
expect(harness.processCalls[0].content).toBe("S");
|
|
305
|
+
harness.resolveProcess();
|
|
306
|
+
|
|
307
|
+
// 5. No chat message side effect on the origin conversation — neither
|
|
308
|
+
// the LLM pipeline nor the `[User action on app: ...]` text echo.
|
|
309
|
+
expect(ctx.enqueueCalls).toHaveLength(0);
|
|
310
|
+
expect(ctx.processCalls).toHaveLength(0);
|
|
311
|
+
const anyUserActionEcho = ctx.sent.some(
|
|
312
|
+
(msg) =>
|
|
313
|
+
"text" in msg &&
|
|
314
|
+
typeof msg.text === "string" &&
|
|
315
|
+
msg.text.includes("[User action on app:"),
|
|
316
|
+
);
|
|
317
|
+
expect(anyUserActionEcho).toBe(false);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("returns error when title or seedPrompt is missing", async () => {
|
|
321
|
+
const ctx = makeContext();
|
|
322
|
+
registerCardSurface(ctx, "surface-2");
|
|
323
|
+
|
|
324
|
+
// Missing seedPrompt.
|
|
325
|
+
const missingSeed = await handleSurfaceAction(ctx, "surface-2", "launch", {
|
|
326
|
+
_action: "launch_conversation",
|
|
327
|
+
title: "T",
|
|
328
|
+
});
|
|
329
|
+
expect(missingSeed).toEqual({
|
|
330
|
+
accepted: false,
|
|
331
|
+
error: "missing_title_or_seedPrompt",
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Missing title.
|
|
335
|
+
const missingTitle = await handleSurfaceAction(ctx, "surface-2", "launch", {
|
|
336
|
+
_action: "launch_conversation",
|
|
337
|
+
seedPrompt: "S",
|
|
338
|
+
});
|
|
339
|
+
expect(missingTitle).toEqual({
|
|
340
|
+
accepted: false,
|
|
341
|
+
error: "missing_title_or_seedPrompt",
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Neither field: still the same validation error.
|
|
345
|
+
const missingBoth = await handleSurfaceAction(ctx, "surface-2", "launch", {
|
|
346
|
+
_action: "launch_conversation",
|
|
347
|
+
});
|
|
348
|
+
expect(missingBoth).toEqual({
|
|
349
|
+
accepted: false,
|
|
350
|
+
error: "missing_title_or_seedPrompt",
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// No launch-side effects in any of the failed validations — no events,
|
|
354
|
+
// no queued origin-conversation messages.
|
|
355
|
+
expect(publishCalls).toHaveLength(0);
|
|
356
|
+
expect(ctx.enqueueCalls).toHaveLength(0);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("omits originTrustContext when origin conversation has none", async () => {
|
|
360
|
+
nextKeyStoreResult = { conversationId: "conv-launched-3" };
|
|
361
|
+
const harness = setupLaunchDeps();
|
|
362
|
+
// No `trustContext` on the origin context — simulating the
|
|
363
|
+
// no-inherited-guardian path.
|
|
364
|
+
const ctx = makeContext();
|
|
365
|
+
registerCardSurface(ctx, "surface-3");
|
|
366
|
+
|
|
367
|
+
const result = await handleSurfaceAction(ctx, "surface-3", "launch", {
|
|
368
|
+
_action: "launch_conversation",
|
|
369
|
+
title: "T",
|
|
370
|
+
seedPrompt: "S",
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
expect(result).toEqual({
|
|
374
|
+
accepted: true,
|
|
375
|
+
conversationId: "conv-launched-3",
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Trust context was never applied to the spawned conversation.
|
|
379
|
+
expect(harness.lastTrustContext()).toBeNull();
|
|
380
|
+
|
|
381
|
+
// Still exactly one open_conversation event with focus: false.
|
|
382
|
+
const openEvents = openConversationEvents();
|
|
383
|
+
expect(openEvents).toHaveLength(1);
|
|
384
|
+
expect(openEvents[0].message.focus).toBe(false);
|
|
385
|
+
|
|
386
|
+
await harness.processStarted;
|
|
387
|
+
harness.resolveProcess();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("handler returns before the seed turn resolves (fire-and-forget)", async () => {
|
|
391
|
+
nextKeyStoreResult = { conversationId: "conv-nonblocking" };
|
|
392
|
+
const harness = setupLaunchDeps();
|
|
393
|
+
const ctx = makeContext();
|
|
394
|
+
registerCardSurface(ctx, "surface-4");
|
|
395
|
+
|
|
396
|
+
// The harness's `persistAndProcessMessage` returns a pending Promise
|
|
397
|
+
// that only resolves when we call `resolveProcess()`. If the helper
|
|
398
|
+
// (or handler) awaited it, `await handleSurfaceAction(...)` below
|
|
399
|
+
// would hang. The fact that it resolves while the seed turn is still
|
|
400
|
+
// pending proves the fire-and-forget behavior that the HTTP route
|
|
401
|
+
// relies on for the fan-out multi-launch UX.
|
|
402
|
+
const result = await handleSurfaceAction(ctx, "surface-4", "launch", {
|
|
403
|
+
_action: "launch_conversation",
|
|
404
|
+
title: "T",
|
|
405
|
+
seedPrompt: "S",
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
expect(result).toEqual({
|
|
409
|
+
accepted: true,
|
|
410
|
+
conversationId: "conv-nonblocking",
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Seed turn is in-flight but not yet resolved. Prove the helper
|
|
414
|
+
// actually invoked it (so we know fire-and-forget is wired), then
|
|
415
|
+
// resolve it to clean up.
|
|
416
|
+
await harness.processStarted;
|
|
417
|
+
expect(harness.processCalls).toHaveLength(1);
|
|
418
|
+
harness.resolveProcess();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test("seed turn rejection is swallowed by the helper's .catch()", async () => {
|
|
422
|
+
nextKeyStoreResult = { conversationId: "conv-seed-fails" };
|
|
423
|
+
const harness = setupLaunchDeps();
|
|
424
|
+
const ctx = makeContext();
|
|
425
|
+
registerCardSurface(ctx, "surface-5");
|
|
426
|
+
|
|
427
|
+
const result = await handleSurfaceAction(ctx, "surface-5", "launch", {
|
|
428
|
+
_action: "launch_conversation",
|
|
429
|
+
title: "T",
|
|
430
|
+
seedPrompt: "boom",
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
expect(result).toEqual({
|
|
434
|
+
accepted: true,
|
|
435
|
+
conversationId: "conv-seed-fails",
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Reject the pending seed turn — the helper's `.catch()` handler
|
|
439
|
+
// must swallow it. If it didn't, Bun would surface the unhandled
|
|
440
|
+
// rejection at test-end and this test would fail.
|
|
441
|
+
await harness.processStarted;
|
|
442
|
+
harness.rejectProcess(new Error("seed-turn-failed"));
|
|
443
|
+
// Give the microtask queue a tick so the `.catch()` runs before
|
|
444
|
+
// the test completes.
|
|
445
|
+
await Promise.resolve();
|
|
446
|
+
await Promise.resolve();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test("dispatches launch even when pendingSurfaceActions has an entry for the surface (first-click case)", async () => {
|
|
450
|
+
// Regression for the gap that left the launch branch unreachable on the
|
|
451
|
+
// FIRST click of a freshly-rendered persistent launcher card. `ui_show`
|
|
452
|
+
// unconditionally sets `pendingSurfaceActions` for any interactive card
|
|
453
|
+
// (regardless of `persistent`), so without this fix `handleSurfaceAction`
|
|
454
|
+
// saw `pending` truthy, skipped the launch dispatch, and fell through to
|
|
455
|
+
// the pending path — emitting the `[User action on card surface: ...]`
|
|
456
|
+
// message and triggering a full LLM round-trip on every click. The plan
|
|
457
|
+
// claimed to eliminate that round-trip; this test enforces it.
|
|
458
|
+
nextKeyStoreResult = { conversationId: "conv-pending-set" };
|
|
459
|
+
const harness = setupLaunchDeps();
|
|
460
|
+
const ctx = makeContext();
|
|
461
|
+
registerCardSurface(ctx, "surface-pending");
|
|
462
|
+
// Simulate `ui_show` having stamped a pending entry for this surface
|
|
463
|
+
// (which it does for any interactive card, including persistent ones).
|
|
464
|
+
ctx.pendingSurfaceActions.set("surface-pending", { surfaceType: "card" });
|
|
465
|
+
|
|
466
|
+
const result = await handleSurfaceAction(ctx, "surface-pending", "launch", {
|
|
467
|
+
_action: "launch_conversation",
|
|
468
|
+
title: "T",
|
|
469
|
+
seedPrompt: "S",
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
expect(result).toEqual({
|
|
473
|
+
accepted: true,
|
|
474
|
+
conversationId: "conv-pending-set",
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// Exactly one open_conversation event with focus: false — the launch
|
|
478
|
+
// branch ran, not the pending fallthrough.
|
|
479
|
+
const openEvents = openConversationEvents();
|
|
480
|
+
expect(openEvents).toHaveLength(1);
|
|
481
|
+
expect(openEvents[0].message.conversationId).toBe("conv-pending-set");
|
|
482
|
+
expect(openEvents[0].message.focus).toBe(false);
|
|
483
|
+
|
|
484
|
+
// Critical: NO message was enqueued onto the origin conversation. If the
|
|
485
|
+
// launch dispatch had fallen through to the pending path, the
|
|
486
|
+
// `[User action on card surface: ...]` text would have been enqueued and
|
|
487
|
+
// an LLM turn would have started.
|
|
488
|
+
expect(ctx.enqueueCalls).toHaveLength(0);
|
|
489
|
+
|
|
490
|
+
// Pending entry was deleted so subsequent sibling clicks on the same
|
|
491
|
+
// persistent card aren't blocked behind a stale "owes-an-answer" flag.
|
|
492
|
+
expect(ctx.pendingSurfaceActions.has("surface-pending")).toBe(false);
|
|
493
|
+
|
|
494
|
+
await harness.processStarted;
|
|
495
|
+
harness.resolveProcess();
|
|
496
|
+
});
|
|
497
|
+
});
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for `isToolActiveForContext` host-tool capability gating.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Scenarios verified:
|
|
5
5
|
* - chrome-extension is its own executor and is exempt from the hasNoClient
|
|
6
6
|
* gate (the extension's own popup UI gates commands; there is no SSE
|
|
7
7
|
* interactive approval channel, and chrome-extension turns intentionally
|
|
8
8
|
* run with `hasNoClient: true` because chrome-extension is not in
|
|
9
9
|
* `INTERACTIVE_INTERFACES`).
|
|
10
|
-
* - macos
|
|
11
|
-
*
|
|
10
|
+
* - macos requires a connected SSE client for host tools that flow through
|
|
11
|
+
* the proxy (e.g. host_bash, host_file_*), so `hasNoClient: true` denies
|
|
12
|
+
* those on macos.
|
|
13
|
+
* - host_browser is NOT in the macos capability set because the proxy path
|
|
14
|
+
* requires a Chrome extension that isn't guaranteed to be attached; macos
|
|
15
|
+
* browser tools fall back to local Playwright Chromium.
|
|
12
16
|
*
|
|
13
17
|
* The per-capability check (`supportsHostProxy(transport, capability)`) runs
|
|
14
18
|
* first and is authoritative for structural support, so host_bash and
|
|
15
19
|
* host_file_* are filtered out for chrome-extension regardless of the
|
|
16
|
-
* hasNoClient flag.
|
|
20
|
+
* hasNoClient flag, and host_browser is filtered out for macos.
|
|
17
21
|
*/
|
|
18
22
|
|
|
19
23
|
import { describe, expect, test } from "bun:test";
|
|
@@ -70,18 +74,23 @@ describe("isToolActiveForContext — host tool capability gating", () => {
|
|
|
70
74
|
).toBe(false);
|
|
71
75
|
});
|
|
72
76
|
|
|
73
|
-
test("host_browser is active for macOS
|
|
77
|
+
test("host_browser is NOT active for macOS (uses local Playwright)", () => {
|
|
78
|
+
// host_browser is not in the macos capability set because the proxy path
|
|
79
|
+
// requires a Chrome extension that isn't guaranteed to be attached; macos
|
|
80
|
+
// browser tools fall back to local Playwright Chromium instead. The
|
|
81
|
+
// per-capability check is authoritative, so host_browser is filtered out
|
|
82
|
+
// for macos regardless of client connection state.
|
|
74
83
|
expect(
|
|
75
84
|
isToolActiveForContext(
|
|
76
85
|
"host_browser",
|
|
77
86
|
makeCtx({ hasNoClient: false, transportInterface: "macos" }),
|
|
78
87
|
),
|
|
79
|
-
).toBe(
|
|
88
|
+
).toBe(false);
|
|
80
89
|
});
|
|
81
90
|
|
|
82
91
|
test("host_browser is NOT active for macOS when hasNoClient is true", () => {
|
|
83
|
-
//
|
|
84
|
-
//
|
|
92
|
+
// Same capability gate as above: host_browser is unsupported on macos
|
|
93
|
+
// regardless of connection state.
|
|
85
94
|
expect(
|
|
86
95
|
isToolActiveForContext(
|
|
87
96
|
"host_browser",
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invariant: the analyze-deps singleton must be populated before the memory
|
|
3
|
+
* worker starts, so any `conversation_analyze` job the worker claims on its
|
|
4
|
+
* first poll sees a non-null deps bundle.
|
|
5
|
+
*
|
|
6
|
+
* Assertions:
|
|
7
|
+
* 1. Source-ordering guard: `lifecycle.ts` constructs `RuntimeHttpServer`
|
|
8
|
+
* (which synchronously calls `setAnalysisDeps()` inside
|
|
9
|
+
* `buildRouteTable()`) before invoking `void initializeQdrantAndMemory()`
|
|
10
|
+
* (which kicks off the memory worker).
|
|
11
|
+
* 2. Runtime check: constructing `RuntimeHttpServer` with `sendMessageDeps`
|
|
12
|
+
* populates the analyze-deps singleton synchronously by the time the
|
|
13
|
+
* constructor returns.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
19
|
+
|
|
20
|
+
mock.module("../../util/logger.js", () => ({
|
|
21
|
+
getLogger: () =>
|
|
22
|
+
new Proxy({} as Record<string, unknown>, {
|
|
23
|
+
get: () => () => {},
|
|
24
|
+
}),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
mock.module("../../config/env.js", () => ({
|
|
28
|
+
isHttpAuthDisabled: () => true,
|
|
29
|
+
hasUngatedHttpAuthDisabled: () => false,
|
|
30
|
+
getGatewayInternalBaseUrl: () => "http://127.0.0.1:7830",
|
|
31
|
+
getGatewayPort: () => 7830,
|
|
32
|
+
getRuntimeHttpPort: () => 7821,
|
|
33
|
+
getRuntimeHttpHost: () => "127.0.0.1",
|
|
34
|
+
getRuntimeGatewayOriginSecret: () => undefined,
|
|
35
|
+
getIngressPublicBaseUrl: () => undefined,
|
|
36
|
+
setIngressPublicBaseUrl: () => {},
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
mock.module("../../config/loader.js", () => ({
|
|
40
|
+
getConfig: () => ({
|
|
41
|
+
ui: {},
|
|
42
|
+
model: "test",
|
|
43
|
+
provider: "test",
|
|
44
|
+
memory: { enabled: false },
|
|
45
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
46
|
+
secretDetection: { enabled: false },
|
|
47
|
+
}),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
import { initializeDb, resetDb } from "../../memory/db.js";
|
|
51
|
+
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
|
|
52
|
+
import { RuntimeHttpServer } from "../../runtime/http-server.js";
|
|
53
|
+
import { getAnalysisDeps } from "../../runtime/services/analyze-deps-singleton.js";
|
|
54
|
+
|
|
55
|
+
initializeDb();
|
|
56
|
+
|
|
57
|
+
describe("daemon lifecycle startup ordering", () => {
|
|
58
|
+
let server: RuntimeHttpServer | null = null;
|
|
59
|
+
|
|
60
|
+
beforeEach(async () => {
|
|
61
|
+
await server?.stop();
|
|
62
|
+
server = null;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterAll(async () => {
|
|
66
|
+
await server?.stop();
|
|
67
|
+
resetDb();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("lifecycle.ts constructs RuntimeHttpServer before kicking off initializeQdrantAndMemory", () => {
|
|
71
|
+
// Source-level guard: read lifecycle.ts and assert the RuntimeHttpServer
|
|
72
|
+
// constructor call appears before the fire-and-forget memory init.
|
|
73
|
+
const lifecyclePath = join(
|
|
74
|
+
import.meta.dir,
|
|
75
|
+
"..",
|
|
76
|
+
"lifecycle.ts",
|
|
77
|
+
);
|
|
78
|
+
const content = readFileSync(lifecyclePath, "utf-8");
|
|
79
|
+
|
|
80
|
+
const httpServerCtorIdx = content.indexOf("new RuntimeHttpServer(");
|
|
81
|
+
const initQdrantIdx = content.indexOf("void initializeQdrantAndMemory(");
|
|
82
|
+
|
|
83
|
+
expect(
|
|
84
|
+
httpServerCtorIdx,
|
|
85
|
+
"Expected to find `new RuntimeHttpServer(` in lifecycle.ts",
|
|
86
|
+
).toBeGreaterThan(-1);
|
|
87
|
+
expect(
|
|
88
|
+
initQdrantIdx,
|
|
89
|
+
"Expected to find `void initializeQdrantAndMemory(` in lifecycle.ts",
|
|
90
|
+
).toBeGreaterThan(-1);
|
|
91
|
+
|
|
92
|
+
expect(
|
|
93
|
+
httpServerCtorIdx,
|
|
94
|
+
"lifecycle.ts must construct RuntimeHttpServer (which synchronously " +
|
|
95
|
+
"populates the analyze-deps singleton via buildRouteTable → " +
|
|
96
|
+
"setAnalysisDeps) BEFORE invoking `void initializeQdrantAndMemory()`. " +
|
|
97
|
+
"Otherwise the memory worker can claim a leftover " +
|
|
98
|
+
"`conversation_analyze` job before the deps singleton is populated, " +
|
|
99
|
+
"the handler throws, and the worker classifies the plain Error as " +
|
|
100
|
+
"fatal and drops the job.",
|
|
101
|
+
).toBeLessThan(initQdrantIdx);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("constructing RuntimeHttpServer with sendMessageDeps populates the analyze-deps singleton synchronously", async () => {
|
|
105
|
+
// Runtime guard: confirms the wiring inside buildRouteTable calls
|
|
106
|
+
// setAnalysisDeps when sendMessageDeps is provided. If that call ever
|
|
107
|
+
// moves out of buildRouteTable without an equivalent call site in
|
|
108
|
+
// lifecycle.ts, this assertion fires.
|
|
109
|
+
server = new RuntimeHttpServer({
|
|
110
|
+
port: 0,
|
|
111
|
+
bearerToken: "test-bearer-token",
|
|
112
|
+
sendMessageDeps: {
|
|
113
|
+
getOrCreateConversation: async () => {
|
|
114
|
+
throw new Error("not used in this test");
|
|
115
|
+
},
|
|
116
|
+
assistantEventHub,
|
|
117
|
+
resolveAttachments: () => [],
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// The constructor populates the singleton synchronously — no start()
|
|
122
|
+
// call required. The memory worker's first tick runs as a microtask
|
|
123
|
+
// after lifecycle.ts kicks it off, so the singleton must be ready by
|
|
124
|
+
// the time the constructor returns.
|
|
125
|
+
expect(getAnalysisDeps()).not.toBeNull();
|
|
126
|
+
});
|
|
127
|
+
});
|