@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
|
@@ -77,10 +77,13 @@ let getOrCreateSessionPageMock: ReturnType<typeof mock>;
|
|
|
77
77
|
let clearSnapshotBackendNodeMapMock: ReturnType<typeof mock>;
|
|
78
78
|
let positionWindowSidebarMock: ReturnType<typeof mock>;
|
|
79
79
|
|
|
80
|
+
const preferredBackendKinds = new Map<string, string>();
|
|
81
|
+
|
|
80
82
|
mock.module("../tools/browser/browser-manager.js", () => {
|
|
81
83
|
getOrCreateSessionPageMock = mock(async () => mockPage);
|
|
82
84
|
clearSnapshotBackendNodeMapMock = mock(() => {});
|
|
83
85
|
positionWindowSidebarMock = mock(async () => {});
|
|
86
|
+
preferredBackendKinds.clear();
|
|
84
87
|
return {
|
|
85
88
|
browserManager: {
|
|
86
89
|
getOrCreateSessionPage: getOrCreateSessionPageMock,
|
|
@@ -88,6 +91,14 @@ mock.module("../tools/browser/browser-manager.js", () => {
|
|
|
88
91
|
supportsRouteInterception: true,
|
|
89
92
|
isInteractive: () => false,
|
|
90
93
|
positionWindowSidebar: positionWindowSidebarMock,
|
|
94
|
+
getPreferredBackendKind: (conversationId: string) =>
|
|
95
|
+
preferredBackendKinds.get(conversationId) ?? null,
|
|
96
|
+
setPreferredBackendKind: (conversationId: string, kind: string) => {
|
|
97
|
+
preferredBackendKinds.set(conversationId, kind);
|
|
98
|
+
},
|
|
99
|
+
clearPreferredBackendKind: (conversationId: string) => {
|
|
100
|
+
preferredBackendKinds.delete(conversationId);
|
|
101
|
+
},
|
|
91
102
|
},
|
|
92
103
|
};
|
|
93
104
|
});
|
|
@@ -101,12 +112,14 @@ mock.module("../tools/browser/browser-screencast.js", () => ({
|
|
|
101
112
|
|
|
102
113
|
// Default url-safety: allow everything
|
|
103
114
|
let parseUrlResult: URL | null = null;
|
|
115
|
+
let parseUrlMock: (input: unknown) => URL | null = () => parseUrlResult;
|
|
104
116
|
let isPrivateResult = false;
|
|
117
|
+
let isPrivateHostMock: (hostname: string) => boolean = () => isPrivateResult;
|
|
105
118
|
let resolveResult: { blockedAddress?: string } = {};
|
|
106
119
|
|
|
107
120
|
mock.module("../tools/network/url-safety.js", () => ({
|
|
108
|
-
parseUrl: (
|
|
109
|
-
isPrivateOrLocalHost: () =>
|
|
121
|
+
parseUrl: (input: unknown) => parseUrlMock(input),
|
|
122
|
+
isPrivateOrLocalHost: (hostname: string) => isPrivateHostMock(hostname),
|
|
110
123
|
resolveHostAddresses: async () => [],
|
|
111
124
|
resolveRequestAddress: async () => resolveResult,
|
|
112
125
|
sanitizeUrlForOutput: (url: URL) => url.href,
|
|
@@ -195,7 +208,9 @@ function resetCdp() {
|
|
|
195
208
|
describe("executeBrowserNavigate", () => {
|
|
196
209
|
beforeEach(() => {
|
|
197
210
|
parseUrlResult = null;
|
|
211
|
+
parseUrlMock = () => parseUrlResult;
|
|
198
212
|
isPrivateResult = false;
|
|
213
|
+
isPrivateHostMock = () => isPrivateResult;
|
|
199
214
|
resolveResult = {};
|
|
200
215
|
resetMockPage();
|
|
201
216
|
resetCdp();
|
|
@@ -557,9 +572,9 @@ describe("executeBrowserNavigate", () => {
|
|
|
557
572
|
expect(cdpDisposed).toBe(true);
|
|
558
573
|
});
|
|
559
574
|
|
|
560
|
-
// ── Extension path (no
|
|
575
|
+
// ── Extension path (no Playwright route interception) ──────────
|
|
561
576
|
|
|
562
|
-
test("extension path skips
|
|
577
|
+
test("extension path skips Playwright route interception", async () => {
|
|
563
578
|
parseUrlResult = new URL("https://example.com/page");
|
|
564
579
|
// Supplying a non-null hostBrowserProxy on the context routes the
|
|
565
580
|
// mocked getCdpClient to the extension path (it mirrors the real
|
|
@@ -578,11 +593,133 @@ describe("executeBrowserNavigate", () => {
|
|
|
578
593
|
);
|
|
579
594
|
|
|
580
595
|
expect(result.isError).toBe(false);
|
|
581
|
-
// Extension path never installs or removes a Playwright route
|
|
596
|
+
// Extension path never installs or removes a Playwright route
|
|
597
|
+
// (route interception only works on the local Playwright path).
|
|
582
598
|
expect(mockPage.route.mock.calls.length).toBe(routeCallsBefore);
|
|
583
599
|
expect(mockPage.unroute.mock.calls.length).toBe(unrouteCallsBefore);
|
|
584
600
|
// Page.navigate still goes through the CdpClient.
|
|
585
601
|
expect(cdpSendCalls.some((c) => c.method === "Page.navigate")).toBe(true);
|
|
586
602
|
expect(cdpDisposed).toBe(true);
|
|
587
603
|
});
|
|
604
|
+
|
|
605
|
+
test("extension path blocks redirects via post-navigation final URL check", async () => {
|
|
606
|
+
// The initial URL is public and passes pre-flight checks. The
|
|
607
|
+
// extension path has no Playwright route interception, but the
|
|
608
|
+
// post-navigation defense-in-depth check catches the private final URL.
|
|
609
|
+
parseUrlResult = new URL("https://public.example.com/start");
|
|
610
|
+
isPrivateResult = false;
|
|
611
|
+
|
|
612
|
+
// Configure mocks to return different results for the initial URL
|
|
613
|
+
// vs. the final URL returned by navigateAndWait.
|
|
614
|
+
parseUrlMock = (input: unknown) => {
|
|
615
|
+
if (typeof input === "string" && input.includes("127.0.0.1")) {
|
|
616
|
+
return new URL(input);
|
|
617
|
+
}
|
|
618
|
+
return parseUrlResult;
|
|
619
|
+
};
|
|
620
|
+
isPrivateHostMock = (hostname: string) => {
|
|
621
|
+
return hostname === "127.0.0.1";
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
// navigateAndWait returns a private final URL (simulating a
|
|
625
|
+
// server-side redirect).
|
|
626
|
+
cdpSendHandler = (method, params) => {
|
|
627
|
+
if (method === "Page.navigate") return { frameId: "f1" };
|
|
628
|
+
if (method === "Runtime.evaluate") {
|
|
629
|
+
const expression = String(params?.["expression"] ?? "");
|
|
630
|
+
if (expression === "document.location.href") {
|
|
631
|
+
return { result: { value: "about:blank" } };
|
|
632
|
+
}
|
|
633
|
+
if (
|
|
634
|
+
expression.includes("readyState") &&
|
|
635
|
+
expression.includes("document.location.href")
|
|
636
|
+
) {
|
|
637
|
+
return {
|
|
638
|
+
result: {
|
|
639
|
+
value: {
|
|
640
|
+
readyState: "complete",
|
|
641
|
+
href: "http://127.0.0.1/admin",
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
return { result: { value: null } };
|
|
647
|
+
}
|
|
648
|
+
return {};
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
const extensionCtx: ToolContext = {
|
|
652
|
+
...ctx,
|
|
653
|
+
hostBrowserProxy: {} as unknown as ToolContext["hostBrowserProxy"],
|
|
654
|
+
};
|
|
655
|
+
const result = await executeBrowserNavigate(
|
|
656
|
+
{ url: "https://public.example.com/start" },
|
|
657
|
+
extensionCtx,
|
|
658
|
+
);
|
|
659
|
+
expect(result.isError).toBe(true);
|
|
660
|
+
expect(result.content).toContain("Navigation blocked");
|
|
661
|
+
expect(result.content).toContain("Final URL resolved to a local/private");
|
|
662
|
+
expect(result.content).toContain("allow_private_network=true");
|
|
663
|
+
expect(cdpDisposed).toBe(true);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// ── Defense-in-depth: post-navigation final URL check ─────────
|
|
667
|
+
|
|
668
|
+
test("post-nav check blocks when final URL resolves to private target", async () => {
|
|
669
|
+
// The initial URL is public and passes pre-flight checks, but the
|
|
670
|
+
// final URL (after redirect) resolves to a private address. The
|
|
671
|
+
// route handler is NOT triggered (navigation succeeds), so only the
|
|
672
|
+
// post-navigation defense-in-depth check catches it.
|
|
673
|
+
parseUrlResult = new URL("https://public.example.com/redirect");
|
|
674
|
+
isPrivateResult = false;
|
|
675
|
+
|
|
676
|
+
// Configure parseUrlMock to return different results for the initial
|
|
677
|
+
// URL vs. the final URL returned by navigateAndWait.
|
|
678
|
+
parseUrlMock = (input: unknown) => {
|
|
679
|
+
if (typeof input === "string" && input.includes("192.168")) {
|
|
680
|
+
return new URL(input);
|
|
681
|
+
}
|
|
682
|
+
return parseUrlResult;
|
|
683
|
+
};
|
|
684
|
+
isPrivateHostMock = (hostname: string) => {
|
|
685
|
+
return hostname === "192.168.1.1";
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// navigateAndWait returns a private final URL (simulating a
|
|
689
|
+
// server-side redirect that the route handler didn't catch).
|
|
690
|
+
cdpSendHandler = (method, params) => {
|
|
691
|
+
if (method === "Page.navigate") return { frameId: "f1" };
|
|
692
|
+
if (method === "Runtime.evaluate") {
|
|
693
|
+
const expression = String(params?.["expression"] ?? "");
|
|
694
|
+
if (expression === "document.location.href") {
|
|
695
|
+
return { result: { value: "about:blank" } };
|
|
696
|
+
}
|
|
697
|
+
if (
|
|
698
|
+
expression.includes("readyState") &&
|
|
699
|
+
expression.includes("document.location.href")
|
|
700
|
+
) {
|
|
701
|
+
return {
|
|
702
|
+
result: {
|
|
703
|
+
value: {
|
|
704
|
+
readyState: "complete",
|
|
705
|
+
href: "http://192.168.1.1/admin",
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
return { result: { value: null } };
|
|
711
|
+
}
|
|
712
|
+
return {};
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
const result = await executeBrowserNavigate(
|
|
716
|
+
{ url: "https://public.example.com/redirect" },
|
|
717
|
+
ctx,
|
|
718
|
+
);
|
|
719
|
+
expect(result.isError).toBe(true);
|
|
720
|
+
expect(result.content).toContain("Navigation blocked");
|
|
721
|
+
expect(result.content).toContain("Final URL resolved to a local/private");
|
|
722
|
+
expect(result.content).toContain("allow_private_network=true");
|
|
723
|
+
expect(cdpDisposed).toBe(true);
|
|
724
|
+
});
|
|
588
725
|
});
|
|
@@ -76,12 +76,23 @@ let mockPage: {
|
|
|
76
76
|
};
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
const preferredBackendKinds = new Map<string, string>();
|
|
80
|
+
|
|
79
81
|
mock.module("../tools/browser/browser-manager.js", () => {
|
|
82
|
+
preferredBackendKinds.clear();
|
|
80
83
|
return {
|
|
81
84
|
browserManager: {
|
|
82
85
|
getOrCreateSessionPage: async () => mockPage,
|
|
83
86
|
closeSessionPage: async () => {},
|
|
84
87
|
closeAllPages: async () => {},
|
|
88
|
+
getPreferredBackendKind: (conversationId: string) =>
|
|
89
|
+
preferredBackendKinds.get(conversationId) ?? null,
|
|
90
|
+
setPreferredBackendKind: (conversationId: string, kind: string) => {
|
|
91
|
+
preferredBackendKinds.set(conversationId, kind);
|
|
92
|
+
},
|
|
93
|
+
clearPreferredBackendKind: (conversationId: string) => {
|
|
94
|
+
preferredBackendKinds.delete(conversationId);
|
|
95
|
+
},
|
|
85
96
|
},
|
|
86
97
|
};
|
|
87
98
|
});
|
|
@@ -35,9 +35,11 @@ let closeAllPagesMock: ReturnType<typeof mock>;
|
|
|
35
35
|
let clearSnapshotBackendNodeMapMock: ReturnType<typeof mock>;
|
|
36
36
|
let storeSnapshotBackendNodeMapMock: ReturnType<typeof mock>;
|
|
37
37
|
let storedBackendNodeMaps: Map<string, Map<string, number>>;
|
|
38
|
+
const preferredBackendKinds = new Map<string, string>();
|
|
38
39
|
|
|
39
40
|
mock.module("../tools/browser/browser-manager.js", () => {
|
|
40
41
|
storedBackendNodeMaps = new Map();
|
|
42
|
+
preferredBackendKinds.clear();
|
|
41
43
|
closeSessionPageMock = mock(async () => {});
|
|
42
44
|
closeAllPagesMock = mock(async () => {});
|
|
43
45
|
clearSnapshotBackendNodeMapMock = mock((conversationId: string) => {
|
|
@@ -80,6 +82,14 @@ mock.module("../tools/browser/browser-manager.js", () => {
|
|
|
80
82
|
if (!map) return null;
|
|
81
83
|
return map.get(elementId) ?? null;
|
|
82
84
|
},
|
|
85
|
+
getPreferredBackendKind: (conversationId: string) =>
|
|
86
|
+
preferredBackendKinds.get(conversationId) ?? null,
|
|
87
|
+
setPreferredBackendKind: (conversationId: string, kind: string) => {
|
|
88
|
+
preferredBackendKinds.set(conversationId, kind);
|
|
89
|
+
},
|
|
90
|
+
clearPreferredBackendKind: (conversationId: string) => {
|
|
91
|
+
preferredBackendKinds.delete(conversationId);
|
|
92
|
+
},
|
|
83
93
|
},
|
|
84
94
|
};
|
|
85
95
|
});
|
|
@@ -23,6 +23,39 @@ mock.module("../config/loader.js", () => ({
|
|
|
23
23
|
invalidateConfigCache: () => {},
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
+
// ── Guardian persona mock ─────────────────────────────────────────
|
|
27
|
+
//
|
|
28
|
+
// `heartbeat-service.isShallowProfile` reads the guardian persona via
|
|
29
|
+
// `resolveGuardianPersona()` and compares against the exported
|
|
30
|
+
// `GUARDIAN_PERSONA_TEMPLATE` scaffold. We mock the module so each
|
|
31
|
+
// test can seed whatever persona content it needs; the scaffold text
|
|
32
|
+
// below is kept byte-identical to the real template in
|
|
33
|
+
// `persona-resolver.ts` so the "scaffold-only" path triggers a match.
|
|
34
|
+
const GUARDIAN_PERSONA_TEMPLATE = `_ Lines starting with _ are comments - they won't appear in the system prompt
|
|
35
|
+
|
|
36
|
+
# User Profile
|
|
37
|
+
|
|
38
|
+
Store details about your user here. Edit freely - build this over time as you learn about them. Don't be pushy about seeking details, but when you learn something, write it down. More context makes you more useful.
|
|
39
|
+
|
|
40
|
+
- Preferred name/reference:
|
|
41
|
+
- Pronouns:
|
|
42
|
+
- Locale:
|
|
43
|
+
- Work role:
|
|
44
|
+
- Goals:
|
|
45
|
+
- Hobbies/fun:
|
|
46
|
+
- Daily tools:
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
// `resolveGuardianPersona` returns already-stripped + trimmed content
|
|
50
|
+
// (or null for missing/empty files). Tests mutate this variable to
|
|
51
|
+
// drive `isShallowProfile`.
|
|
52
|
+
let mockGuardianPersona: string | null = null;
|
|
53
|
+
|
|
54
|
+
mock.module("../prompts/persona-resolver.js", () => ({
|
|
55
|
+
GUARDIAN_PERSONA_TEMPLATE,
|
|
56
|
+
resolveGuardianPersona: () => mockGuardianPersona,
|
|
57
|
+
}));
|
|
58
|
+
|
|
26
59
|
// Mock conversation store
|
|
27
60
|
const createdConversations: Array<{ title: string; conversationType: string }> =
|
|
28
61
|
[];
|
|
@@ -85,7 +118,13 @@ const IDENTITY_TEMPLATE = readFileSync(
|
|
|
85
118
|
join(templatesDir, "IDENTITY.md"),
|
|
86
119
|
"utf-8",
|
|
87
120
|
);
|
|
88
|
-
|
|
121
|
+
|
|
122
|
+
// Stripped/trimmed form of the guardian persona scaffold — mirrors
|
|
123
|
+
// the transformation applied by `resolveGuardianPersona` (which runs
|
|
124
|
+
// `stripCommentLines` internally). Used to simulate a freshly-seeded,
|
|
125
|
+
// never-edited persona file.
|
|
126
|
+
const { stripCommentLines } = await import("../util/strip-comment-lines.js");
|
|
127
|
+
const SCAFFOLD_PERSONA = stripCommentLines(GUARDIAN_PERSONA_TEMPLATE).trim();
|
|
89
128
|
|
|
90
129
|
describe("HeartbeatService", () => {
|
|
91
130
|
let processMessageCalls: Array<{
|
|
@@ -99,7 +138,6 @@ describe("HeartbeatService", () => {
|
|
|
99
138
|
// Clean up workspace files between tests so file-existence tests don't leak
|
|
100
139
|
rmSync(join(testWorkspaceDir, "HEARTBEAT.md"), { force: true });
|
|
101
140
|
rmSync(join(testWorkspaceDir, "IDENTITY.md"), { force: true });
|
|
102
|
-
rmSync(join(testWorkspaceDir, "USER.md"), { force: true });
|
|
103
141
|
rmSync(join(testWorkspaceDir, ".reengagement-ts"), { force: true });
|
|
104
142
|
});
|
|
105
143
|
|
|
@@ -108,6 +146,7 @@ describe("HeartbeatService", () => {
|
|
|
108
146
|
alerterCalls = [];
|
|
109
147
|
createdConversations.length = 0;
|
|
110
148
|
conversationIdCounter = 0;
|
|
149
|
+
mockGuardianPersona = null;
|
|
111
150
|
|
|
112
151
|
mockConfig = {
|
|
113
152
|
heartbeat: {
|
|
@@ -494,9 +533,23 @@ describe("HeartbeatService", () => {
|
|
|
494
533
|
});
|
|
495
534
|
|
|
496
535
|
describe("isShallowProfile", () => {
|
|
497
|
-
test("returns true when
|
|
536
|
+
test("returns true when IDENTITY.md is template and guardian persona is missing", () => {
|
|
537
|
+
writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
|
|
538
|
+
mockGuardianPersona = null;
|
|
539
|
+
|
|
540
|
+
expect(isShallowProfile()).toBe(true);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
test("returns true when IDENTITY.md is template and guardian persona has only scaffold fields", () => {
|
|
498
544
|
writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
|
|
499
|
-
|
|
545
|
+
mockGuardianPersona = SCAFFOLD_PERSONA;
|
|
546
|
+
|
|
547
|
+
expect(isShallowProfile()).toBe(true);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test("returns true when IDENTITY.md is template and guardian persona is empty string", () => {
|
|
551
|
+
writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
|
|
552
|
+
mockGuardianPersona = "";
|
|
500
553
|
|
|
501
554
|
expect(isShallowProfile()).toBe(true);
|
|
502
555
|
});
|
|
@@ -506,22 +559,22 @@ describe("HeartbeatService", () => {
|
|
|
506
559
|
join(testWorkspaceDir, "IDENTITY.md"),
|
|
507
560
|
"# IDENTITY.md\n\n- **Name:** Jarvis\n",
|
|
508
561
|
);
|
|
509
|
-
|
|
562
|
+
mockGuardianPersona = SCAFFOLD_PERSONA;
|
|
510
563
|
|
|
511
564
|
expect(isShallowProfile()).toBe(false);
|
|
512
565
|
});
|
|
513
566
|
|
|
514
|
-
test("returns false when
|
|
567
|
+
test("returns false when guardian persona has real content", () => {
|
|
515
568
|
writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
"# USER.md\n\n- Preferred name/reference: Alice\n",
|
|
519
|
-
);
|
|
569
|
+
mockGuardianPersona =
|
|
570
|
+
"# User Profile\n\n- Preferred name/reference: Alice\n- Work role: designer";
|
|
520
571
|
|
|
521
572
|
expect(isShallowProfile()).toBe(false);
|
|
522
573
|
});
|
|
523
574
|
|
|
524
|
-
test("returns false when
|
|
575
|
+
test("returns false when IDENTITY.md does not exist", () => {
|
|
576
|
+
mockGuardianPersona = null;
|
|
577
|
+
|
|
525
578
|
expect(isShallowProfile()).toBe(false);
|
|
526
579
|
});
|
|
527
580
|
});
|
|
@@ -529,7 +582,7 @@ describe("HeartbeatService", () => {
|
|
|
529
582
|
describe("relationship-depth prompt injection", () => {
|
|
530
583
|
test("includes <relationship-depth> when profile is shallow", () => {
|
|
531
584
|
writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
|
|
532
|
-
|
|
585
|
+
mockGuardianPersona = SCAFFOLD_PERSONA;
|
|
533
586
|
|
|
534
587
|
const service = createService();
|
|
535
588
|
const { prompt, includedReengagement } =
|
|
@@ -545,7 +598,7 @@ describe("HeartbeatService", () => {
|
|
|
545
598
|
join(testWorkspaceDir, "IDENTITY.md"),
|
|
546
599
|
"# IDENTITY.md\n\n- **Name:** Jarvis\n",
|
|
547
600
|
);
|
|
548
|
-
|
|
601
|
+
mockGuardianPersona = SCAFFOLD_PERSONA;
|
|
549
602
|
|
|
550
603
|
const service = createService();
|
|
551
604
|
const { prompt, includedReengagement } =
|
|
@@ -557,7 +610,7 @@ describe("HeartbeatService", () => {
|
|
|
557
610
|
|
|
558
611
|
test("omits <relationship-depth> when cooldown has not elapsed", () => {
|
|
559
612
|
writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
|
|
560
|
-
|
|
613
|
+
mockGuardianPersona = SCAFFOLD_PERSONA;
|
|
561
614
|
// Write a recent timestamp to simulate cooldown not elapsed
|
|
562
615
|
writeFileSync(
|
|
563
616
|
join(testWorkspaceDir, ".reengagement-ts"),
|
|
@@ -574,7 +627,7 @@ describe("HeartbeatService", () => {
|
|
|
574
627
|
|
|
575
628
|
test("includes <relationship-depth> when cooldown has elapsed", () => {
|
|
576
629
|
writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
|
|
577
|
-
|
|
630
|
+
mockGuardianPersona = SCAFFOLD_PERSONA;
|
|
578
631
|
// Write a timestamp from 19 hours ago
|
|
579
632
|
const nineteenHoursAgo = Date.now() - 19 * 60 * 60 * 1000;
|
|
580
633
|
writeFileSync(
|
|
@@ -592,7 +645,7 @@ describe("HeartbeatService", () => {
|
|
|
592
645
|
|
|
593
646
|
test("does not record timestamp when processMessage fails", async () => {
|
|
594
647
|
writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
|
|
595
|
-
|
|
648
|
+
mockGuardianPersona = SCAFFOLD_PERSONA;
|
|
596
649
|
|
|
597
650
|
const service = createService({
|
|
598
651
|
processMessage: async () => {
|
|
@@ -609,7 +662,7 @@ describe("HeartbeatService", () => {
|
|
|
609
662
|
|
|
610
663
|
test("records timestamp after successful delivery", async () => {
|
|
611
664
|
writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), IDENTITY_TEMPLATE);
|
|
612
|
-
|
|
665
|
+
mockGuardianPersona = SCAFFOLD_PERSONA;
|
|
613
666
|
|
|
614
667
|
const service = createService();
|
|
615
668
|
await service.runOnce();
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
// Stub the OAuth connection store before importing anything that
|
|
13
|
+
// transitively pulls in the writer — otherwise importing the route
|
|
14
|
+
// module would try to open the real OAuth DB.
|
|
15
|
+
mock.module("../oauth/oauth-store.js", () => ({
|
|
16
|
+
listConnections: () => [],
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
// Stub the DB-authoritative conversation count helper so the writer
|
|
20
|
+
// (invoked by the read-through fallback) does not lazy-open a real
|
|
21
|
+
// sqlite handle against a stale or deleted per-test tmpdir.
|
|
22
|
+
mock.module("../memory/conversation-queries.js", () => ({
|
|
23
|
+
countConversations: () => 0,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
const { handleGetHomeState, homeStateRouteDefinitions } =
|
|
27
|
+
await import("../runtime/routes/home-state-routes.js");
|
|
28
|
+
const { writeRelationshipState, getRelationshipStatePath } =
|
|
29
|
+
await import("../home/relationship-state-writer.js");
|
|
30
|
+
|
|
31
|
+
interface RelationshipStateWire {
|
|
32
|
+
version: number;
|
|
33
|
+
assistantId: string;
|
|
34
|
+
tier: number;
|
|
35
|
+
progressPercent: number;
|
|
36
|
+
facts: unknown[];
|
|
37
|
+
capabilities: Array<{ id: string; tier: string }>;
|
|
38
|
+
conversationCount: number;
|
|
39
|
+
hatchedDate: string;
|
|
40
|
+
assistantName: string;
|
|
41
|
+
userName?: string;
|
|
42
|
+
updatedAt: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let workspaceDir: string;
|
|
46
|
+
let origWorkspaceDir: string | undefined;
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "vellum-hsr-"));
|
|
50
|
+
origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
51
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
if (origWorkspaceDir === undefined) {
|
|
56
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
57
|
+
} else {
|
|
58
|
+
process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
62
|
+
} catch {
|
|
63
|
+
// best-effort cleanup
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("home-state-routes", () => {
|
|
68
|
+
describe("route registration", () => {
|
|
69
|
+
test("exposes GET /v1/home/state", () => {
|
|
70
|
+
const routes = homeStateRouteDefinitions();
|
|
71
|
+
expect(routes).toHaveLength(1);
|
|
72
|
+
expect(routes[0].endpoint).toBe("home/state");
|
|
73
|
+
expect(routes[0].method).toBe("GET");
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("handleGetHomeState", () => {
|
|
78
|
+
test("returns persisted state when the JSON file exists", async () => {
|
|
79
|
+
// Seed a minimal USER.md so the writer produces a non-empty
|
|
80
|
+
// state, then let the writer persist it.
|
|
81
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
82
|
+
writeFileSync(
|
|
83
|
+
join(workspaceDir, "USER.md"),
|
|
84
|
+
"- Preferred name: Casey\n- Work role: Engineer\n",
|
|
85
|
+
"utf-8",
|
|
86
|
+
);
|
|
87
|
+
await writeRelationshipState();
|
|
88
|
+
expect(existsSync(getRelationshipStatePath())).toBe(true);
|
|
89
|
+
|
|
90
|
+
const res = await handleGetHomeState();
|
|
91
|
+
expect(res.status).toBe(200);
|
|
92
|
+
const body = (await res.json()) as RelationshipStateWire;
|
|
93
|
+
expect(body.version).toBe(1);
|
|
94
|
+
expect(body.assistantId).toBe("default");
|
|
95
|
+
expect(body.capabilities).toHaveLength(6);
|
|
96
|
+
expect(body.userName).toBe("Casey");
|
|
97
|
+
expect(typeof body.updatedAt).toBe("string");
|
|
98
|
+
expect(Number.isNaN(Date.parse(body.updatedAt))).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("read-through fallback when the file is missing", async () => {
|
|
102
|
+
// Do NOT call the writer — the file should not exist. The
|
|
103
|
+
// route must still succeed via computeRelationshipState().
|
|
104
|
+
expect(existsSync(getRelationshipStatePath())).toBe(false);
|
|
105
|
+
|
|
106
|
+
const res = await handleGetHomeState();
|
|
107
|
+
expect(res.status).toBe(200);
|
|
108
|
+
const body = (await res.json()) as RelationshipStateWire;
|
|
109
|
+
expect(body.version).toBe(1);
|
|
110
|
+
expect(body.tier).toBe(1);
|
|
111
|
+
expect(body.progressPercent).toBe(0);
|
|
112
|
+
expect(body.capabilities).toHaveLength(6);
|
|
113
|
+
expect(body.conversationCount).toBe(0);
|
|
114
|
+
|
|
115
|
+
// Fallback must NOT write the file — that's the writer's job.
|
|
116
|
+
expect(existsSync(getRelationshipStatePath())).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("falls back to compute when the persisted file is malformed", async () => {
|
|
120
|
+
// Write a deliberately broken file at the state path.
|
|
121
|
+
const path = getRelationshipStatePath();
|
|
122
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
123
|
+
writeFileSync(path, "this is not json", "utf-8");
|
|
124
|
+
|
|
125
|
+
const res = await handleGetHomeState();
|
|
126
|
+
expect(res.status).toBe(200);
|
|
127
|
+
const body = (await res.json()) as RelationshipStateWire;
|
|
128
|
+
// Parsed body is a fresh compute, not the garbage on disk.
|
|
129
|
+
expect(body.version).toBe(1);
|
|
130
|
+
expect(body.capabilities).toHaveLength(6);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("GET returns fresh state even when the persisted file is stale", async () => {
|
|
134
|
+
// Persist a snapshot with userName=Casey.
|
|
135
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
136
|
+
writeFileSync(
|
|
137
|
+
join(workspaceDir, "USER.md"),
|
|
138
|
+
"- Preferred name: Casey\n",
|
|
139
|
+
"utf-8",
|
|
140
|
+
);
|
|
141
|
+
await writeRelationshipState();
|
|
142
|
+
expect(existsSync(getRelationshipStatePath())).toBe(true);
|
|
143
|
+
|
|
144
|
+
// Mutate USER.md outside of any turn-boundary writer call. This
|
|
145
|
+
// simulates: a user editing their persona file, OAuth connecting
|
|
146
|
+
// a provider, or a conversation delete flow touching state
|
|
147
|
+
// outside the turn-boundary writer.
|
|
148
|
+
writeFileSync(
|
|
149
|
+
join(workspaceDir, "USER.md"),
|
|
150
|
+
"- Preferred name: Jamie\n",
|
|
151
|
+
"utf-8",
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// The route must return the FRESH value (Jamie), not the
|
|
155
|
+
// cached value (Casey) from the persisted file.
|
|
156
|
+
const res = await handleGetHomeState();
|
|
157
|
+
expect(res.status).toBe(200);
|
|
158
|
+
const body = (await res.json()) as RelationshipStateWire;
|
|
159
|
+
expect(body.userName).toBe("Jamie");
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -405,26 +405,21 @@ describe("HostBashProxy", () => {
|
|
|
405
405
|
function spySignal(source: AbortSignal): Spied {
|
|
406
406
|
const addCalls: string[] = [];
|
|
407
407
|
const removeCalls: string[] = [];
|
|
408
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
409
408
|
const s = source as any;
|
|
410
409
|
const origAdd = source.addEventListener.bind(source);
|
|
411
410
|
const origRemove = source.removeEventListener.bind(source);
|
|
412
411
|
s.addEventListener = (
|
|
413
412
|
type: string,
|
|
414
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
415
413
|
...rest: any[]
|
|
416
414
|
) => {
|
|
417
415
|
addCalls.push(type);
|
|
418
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
419
416
|
return (origAdd as any)(type, ...rest);
|
|
420
417
|
};
|
|
421
418
|
s.removeEventListener = (
|
|
422
419
|
type: string,
|
|
423
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
424
420
|
...rest: any[]
|
|
425
421
|
) => {
|
|
426
422
|
removeCalls.push(type);
|
|
427
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
428
423
|
return (origRemove as any)(type, ...rest);
|
|
429
424
|
};
|
|
430
425
|
return { signal: source, addCalls, removeCalls };
|