@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
|
@@ -21,6 +21,12 @@ import {
|
|
|
21
21
|
import { getDb, initializeDb } from "../memory/db.js";
|
|
22
22
|
import { AssistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
23
23
|
import type { AuthContext } from "../runtime/auth/types.js";
|
|
24
|
+
import {
|
|
25
|
+
__resetChromeExtensionRegistryForTests,
|
|
26
|
+
type ChromeExtensionConnection,
|
|
27
|
+
getChromeExtensionRegistry,
|
|
28
|
+
} from "../runtime/chrome-extension-registry.js";
|
|
29
|
+
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
24
30
|
import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
|
|
25
31
|
|
|
26
32
|
const testDir = process.env.VELLUM_WORKSPACE_DIR!;
|
|
@@ -329,6 +335,270 @@ beforeEach(() => {
|
|
|
329
335
|
resetTables();
|
|
330
336
|
resetConversationsDir();
|
|
331
337
|
conversationInstances.clear();
|
|
338
|
+
pendingInteractions.clear();
|
|
339
|
+
__resetChromeExtensionRegistryForTests();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// ── macOS browser backend fallback regression ─────────────────────────
|
|
343
|
+
//
|
|
344
|
+
// These tests verify the route-level wiring that enables the CDP factory's
|
|
345
|
+
// fallback chain on macOS-originated turns:
|
|
346
|
+
//
|
|
347
|
+
// 1. Extension connected → extension backend (tested in host-browser-e2e-cloud.test.ts)
|
|
348
|
+
// 2. Extension absent, cdp-inspect unavailable → local Playwright fallback
|
|
349
|
+
//
|
|
350
|
+
// Specifically, we verify that when a macOS message enters through
|
|
351
|
+
// handleSendMessage with `interface: "macos"` and NO extension is connected,
|
|
352
|
+
// the conversation's turnInterfaceContext is set correctly so the CDP factory
|
|
353
|
+
// builds the right candidate list (cdp-inspect → local). If cdp-inspect
|
|
354
|
+
// is also unreachable (the common case when Chrome is not launched with
|
|
355
|
+
// --remote-debugging-port), the factory falls through to local.
|
|
356
|
+
//
|
|
357
|
+
// This is the regression guard for backend preference order step 2:
|
|
358
|
+
// macOS + no extension + cdp-inspect unavailable → local backend
|
|
359
|
+
//
|
|
360
|
+
// If the interface propagation or factory candidate list construction
|
|
361
|
+
// regresses, these tests will fail.
|
|
362
|
+
|
|
363
|
+
describe("macOS browser backend fallback (no extension, no cdp-inspect)", () => {
|
|
364
|
+
test("macOS turn without extension sets turnInterfaceContext to macos, enabling local fallback", async () => {
|
|
365
|
+
const conversationKey = `macos-fallback-${crypto.randomUUID()}`;
|
|
366
|
+
const content = "Test macOS fallback path.";
|
|
367
|
+
let capturedConversation: Conversation | undefined;
|
|
368
|
+
|
|
369
|
+
const response = await handleSendMessage(
|
|
370
|
+
new Request("http://localhost/v1/messages", {
|
|
371
|
+
method: "POST",
|
|
372
|
+
headers: { "Content-Type": "application/json" },
|
|
373
|
+
body: JSON.stringify({
|
|
374
|
+
conversationKey,
|
|
375
|
+
content,
|
|
376
|
+
sourceChannel: "vellum",
|
|
377
|
+
interface: "macos",
|
|
378
|
+
}),
|
|
379
|
+
}),
|
|
380
|
+
{
|
|
381
|
+
sendMessageDeps: {
|
|
382
|
+
getOrCreateConversation: async (conversationId: string) => {
|
|
383
|
+
const conv = getOrCreateFakeConversation(conversationId);
|
|
384
|
+
capturedConversation = conv;
|
|
385
|
+
return conv;
|
|
386
|
+
},
|
|
387
|
+
assistantEventHub: new AssistantEventHub(),
|
|
388
|
+
resolveAttachments: () => [],
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
authContext,
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
expect(response.status).toBe(202);
|
|
395
|
+
|
|
396
|
+
// The conversation instance should have its turnInterfaceContext set
|
|
397
|
+
// to "macos" by handleSendMessage. This is the value the CDP factory
|
|
398
|
+
// reads (via ToolContext.transportInterface) to decide whether to
|
|
399
|
+
// include cdp-inspect as a desktop-auto candidate and ultimately fall
|
|
400
|
+
// back to local Playwright when cdp-inspect is unavailable.
|
|
401
|
+
expect(capturedConversation).toBeDefined();
|
|
402
|
+
const interfaceCtx = capturedConversation!.getTurnInterfaceContext();
|
|
403
|
+
expect(interfaceCtx).not.toBeNull();
|
|
404
|
+
expect(interfaceCtx!.userMessageInterface).toBe("macos");
|
|
405
|
+
expect(interfaceCtx!.assistantMessageInterface).toBe("macos");
|
|
406
|
+
|
|
407
|
+
// With no extension in the ChromeExtensionRegistry, the conversation's
|
|
408
|
+
// hostBrowserProxy should NOT be provisioned through the registry.
|
|
409
|
+
// The absence of a hostBrowserProxy means the CDP factory will skip
|
|
410
|
+
// the extension candidate entirely and fall through:
|
|
411
|
+
// cdp-inspect (desktop-auto) → local.
|
|
412
|
+
expect(
|
|
413
|
+
(capturedConversation as unknown as Record<string, unknown>)
|
|
414
|
+
.hostBrowserProxy,
|
|
415
|
+
).toBeUndefined();
|
|
416
|
+
expect(
|
|
417
|
+
(capturedConversation as unknown as Record<string, unknown>)
|
|
418
|
+
.hostBrowserSenderOverride,
|
|
419
|
+
).toBeUndefined();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("macOS turn correctly persists interface metadata through the agent loop", async () => {
|
|
423
|
+
const conversationKey = `macos-metadata-${crypto.randomUUID()}`;
|
|
424
|
+
const content = "Verify interface metadata persistence.";
|
|
425
|
+
|
|
426
|
+
const response = await handleSendMessage(
|
|
427
|
+
new Request("http://localhost/v1/messages", {
|
|
428
|
+
method: "POST",
|
|
429
|
+
headers: { "Content-Type": "application/json" },
|
|
430
|
+
body: JSON.stringify({
|
|
431
|
+
conversationKey,
|
|
432
|
+
content,
|
|
433
|
+
sourceChannel: "vellum",
|
|
434
|
+
interface: "macos",
|
|
435
|
+
}),
|
|
436
|
+
}),
|
|
437
|
+
{
|
|
438
|
+
sendMessageDeps: {
|
|
439
|
+
getOrCreateConversation: async (conversationId: string) =>
|
|
440
|
+
getOrCreateFakeConversation(conversationId),
|
|
441
|
+
assistantEventHub: new AssistantEventHub(),
|
|
442
|
+
resolveAttachments: () => [],
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
authContext,
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
expect(response.status).toBe(202);
|
|
449
|
+
const body = (await response.json()) as {
|
|
450
|
+
accepted: boolean;
|
|
451
|
+
conversationId: string;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// Wait for the agent loop to persist the assistant reply (the fake
|
|
455
|
+
// runAgentLoop persists interface metadata into message records).
|
|
456
|
+
const conversationRow = getConversation(body.conversationId);
|
|
457
|
+
expect(conversationRow).not.toBeNull();
|
|
458
|
+
const conversationDir = getConversationDirPath(
|
|
459
|
+
body.conversationId,
|
|
460
|
+
conversationRow!.createdAt,
|
|
461
|
+
);
|
|
462
|
+
const messagesPath = join(conversationDir, "messages.jsonl");
|
|
463
|
+
|
|
464
|
+
const lines = await waitFor(() => {
|
|
465
|
+
if (!existsSync(messagesPath)) return undefined;
|
|
466
|
+
const raw = readFileSync(messagesPath, "utf-8").trim();
|
|
467
|
+
if (!raw) return undefined;
|
|
468
|
+
const parsed = raw.split("\n").map(
|
|
469
|
+
(line) =>
|
|
470
|
+
JSON.parse(line) as {
|
|
471
|
+
role: string;
|
|
472
|
+
metadata?: Record<string, unknown>;
|
|
473
|
+
},
|
|
474
|
+
);
|
|
475
|
+
return parsed.length >= 2 ? parsed : undefined;
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// The assistant reply (second line) should carry the interface metadata
|
|
479
|
+
// set by setTurnInterfaceContext during the macOS turn setup.
|
|
480
|
+
const assistantLine = lines[1];
|
|
481
|
+
expect(assistantLine?.role).toBe("assistant");
|
|
482
|
+
expect(assistantLine?.metadata?.userMessageInterface).toBe("macos");
|
|
483
|
+
expect(assistantLine?.metadata?.assistantMessageInterface).toBe("macos");
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
describe("chrome-extension host_browser pending registration", () => {
|
|
488
|
+
test("registers pending interaction before registry-routed host_browser send", async () => {
|
|
489
|
+
const guardianId = `guardian-${crypto.randomUUID()}`;
|
|
490
|
+
let pendingRegisteredAtSend: boolean | undefined;
|
|
491
|
+
let observedRequestId: string | undefined;
|
|
492
|
+
|
|
493
|
+
const connection: ChromeExtensionConnection = {
|
|
494
|
+
id: `conn-${crypto.randomUUID()}`,
|
|
495
|
+
guardianId,
|
|
496
|
+
ws: {
|
|
497
|
+
send(payload: string) {
|
|
498
|
+
const frame = JSON.parse(payload) as {
|
|
499
|
+
type?: string;
|
|
500
|
+
requestId?: string;
|
|
501
|
+
};
|
|
502
|
+
if (
|
|
503
|
+
frame.type === "host_browser_request" &&
|
|
504
|
+
typeof frame.requestId === "string"
|
|
505
|
+
) {
|
|
506
|
+
observedRequestId = frame.requestId;
|
|
507
|
+
pendingRegisteredAtSend =
|
|
508
|
+
pendingInteractions.get(frame.requestId)?.kind === "host_browser";
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
close() {},
|
|
512
|
+
} as unknown as ChromeExtensionConnection["ws"],
|
|
513
|
+
connectedAt: Date.now(),
|
|
514
|
+
lastActiveAt: Date.now(),
|
|
515
|
+
};
|
|
516
|
+
getChromeExtensionRegistry().register(connection);
|
|
517
|
+
|
|
518
|
+
const actorAuthContext: AuthContext = {
|
|
519
|
+
...authContext,
|
|
520
|
+
subject: `actor:self:${guardianId}`,
|
|
521
|
+
principalType: "actor",
|
|
522
|
+
scopeProfile: "actor_client_v1",
|
|
523
|
+
actorPrincipalId: guardianId,
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const conversationKey = `chrome-ext-pending-${crypto.randomUUID()}`;
|
|
527
|
+
const response = await handleSendMessage(
|
|
528
|
+
new Request("http://localhost/v1/messages", {
|
|
529
|
+
method: "POST",
|
|
530
|
+
headers: { "Content-Type": "application/json" },
|
|
531
|
+
body: JSON.stringify({
|
|
532
|
+
conversationKey,
|
|
533
|
+
content: "Trigger host browser request via registry sender.",
|
|
534
|
+
sourceChannel: "vellum",
|
|
535
|
+
interface: "chrome-extension",
|
|
536
|
+
}),
|
|
537
|
+
}),
|
|
538
|
+
{
|
|
539
|
+
sendMessageDeps: {
|
|
540
|
+
getOrCreateConversation: async (conversationId: string) => {
|
|
541
|
+
const conversation = createFakeConversation(
|
|
542
|
+
conversationId,
|
|
543
|
+
) as unknown as {
|
|
544
|
+
hostBrowserProxy?: {
|
|
545
|
+
request: (
|
|
546
|
+
input: { cdpMethod: string; timeout_seconds?: number },
|
|
547
|
+
conversationId: string,
|
|
548
|
+
) => Promise<{ content: string; isError: boolean }>;
|
|
549
|
+
};
|
|
550
|
+
processing: boolean;
|
|
551
|
+
abortController: AbortController | null;
|
|
552
|
+
currentRequestId?: string;
|
|
553
|
+
runAgentLoop: (
|
|
554
|
+
content: string,
|
|
555
|
+
userMessageId: string,
|
|
556
|
+
onEvent: (msg: Record<string, unknown>) => void,
|
|
557
|
+
) => Promise<void>;
|
|
558
|
+
conversationId: string;
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
conversation.runAgentLoop = async function (
|
|
562
|
+
_content: string,
|
|
563
|
+
_userMessageId: string,
|
|
564
|
+
_onEvent: (msg: Record<string, unknown>) => void,
|
|
565
|
+
): Promise<void> {
|
|
566
|
+
if (!this.hostBrowserProxy) {
|
|
567
|
+
throw new Error(
|
|
568
|
+
"Expected hostBrowserProxy to be provisioned for chrome-extension turn",
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
await this.hostBrowserProxy.request(
|
|
572
|
+
{
|
|
573
|
+
cdpMethod: "Browser.getVersion",
|
|
574
|
+
// Keep the test fast while still allowing assertion at send time.
|
|
575
|
+
timeout_seconds: 0.05,
|
|
576
|
+
},
|
|
577
|
+
this.conversationId,
|
|
578
|
+
);
|
|
579
|
+
this.processing = false;
|
|
580
|
+
this.abortController = null;
|
|
581
|
+
this.currentRequestId = undefined;
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
conversationInstances.set(
|
|
585
|
+
conversationId,
|
|
586
|
+
conversation as unknown as Conversation,
|
|
587
|
+
);
|
|
588
|
+
return conversation as unknown as Conversation;
|
|
589
|
+
},
|
|
590
|
+
assistantEventHub: new AssistantEventHub(),
|
|
591
|
+
resolveAttachments: () => [],
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
actorAuthContext,
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
expect(response.status).toBe(202);
|
|
598
|
+
const registered = await waitFor(() => pendingRegisteredAtSend, 1000);
|
|
599
|
+
expect(observedRequestId).toBeDefined();
|
|
600
|
+
expect(registered).toBe(true);
|
|
601
|
+
});
|
|
332
602
|
});
|
|
333
603
|
|
|
334
604
|
describe("conversationKey send path disk-view regression", () => {
|
|
@@ -1391,6 +1391,61 @@ describe("buildUnifiedTurnContextBlock", () => {
|
|
|
1391
1391
|
expect(text).toContain("contact_notes: Prefers short replies");
|
|
1392
1392
|
expect(text).toContain("contact_interaction_count: 42");
|
|
1393
1393
|
});
|
|
1394
|
+
|
|
1395
|
+
test("time_since_last_message: emitted right after current_time when provided", () => {
|
|
1396
|
+
const options: UnifiedTurnContextOptions = {
|
|
1397
|
+
timestamp: "2026-04-02T12:00:00Z",
|
|
1398
|
+
interfaceName: "macos",
|
|
1399
|
+
timeSinceLastMessage: "2d ago",
|
|
1400
|
+
};
|
|
1401
|
+
|
|
1402
|
+
const text = buildUnifiedTurnContextBlock(options);
|
|
1403
|
+
const lines = text.split("\n");
|
|
1404
|
+
expect(lines[0]).toBe("<turn_context>");
|
|
1405
|
+
expect(lines[1]).toBe("current_time: 2026-04-02T12:00:00Z");
|
|
1406
|
+
expect(lines[2]).toBe("time_since_last_message: 2d ago");
|
|
1407
|
+
expect(lines[3]).toBe("interface: macos");
|
|
1408
|
+
expect(lines[4]).toBe("</turn_context>");
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
test("time_since_last_message: omitted when null", () => {
|
|
1412
|
+
const options: UnifiedTurnContextOptions = {
|
|
1413
|
+
timestamp: "2026-04-02T12:00:00Z",
|
|
1414
|
+
interfaceName: "macos",
|
|
1415
|
+
timeSinceLastMessage: null,
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1418
|
+
const text = buildUnifiedTurnContextBlock(options);
|
|
1419
|
+
expect(text).not.toContain("time_since_last_message");
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
test("time_since_last_message: omitted when field absent (backward-compat)", () => {
|
|
1423
|
+
const options: UnifiedTurnContextOptions = {
|
|
1424
|
+
timestamp: "2026-04-02T12:00:00Z",
|
|
1425
|
+
interfaceName: "macos",
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1428
|
+
const text = buildUnifiedTurnContextBlock(options);
|
|
1429
|
+
expect(text).not.toContain("time_since_last_message");
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
test("time_since_last_message: works on non-guardian path", () => {
|
|
1433
|
+
const options: UnifiedTurnContextOptions = {
|
|
1434
|
+
timestamp: "2026-04-02T12:00:00Z",
|
|
1435
|
+
interfaceName: "telegram",
|
|
1436
|
+
channelName: "telegram",
|
|
1437
|
+
timeSinceLastMessage: "yesterday",
|
|
1438
|
+
actorContext: {
|
|
1439
|
+
sourceChannel: "telegram",
|
|
1440
|
+
canonicalActorIdentity: "user-1",
|
|
1441
|
+
trustClass: "trusted_contact",
|
|
1442
|
+
},
|
|
1443
|
+
};
|
|
1444
|
+
|
|
1445
|
+
const text = buildUnifiedTurnContextBlock(options);
|
|
1446
|
+
expect(text).toContain("time_since_last_message: yesterday");
|
|
1447
|
+
expect(text).toContain("canonical_actor_identity: user-1");
|
|
1448
|
+
});
|
|
1394
1449
|
});
|
|
1395
1450
|
|
|
1396
1451
|
// ---------------------------------------------------------------------------
|
|
@@ -1829,7 +1829,7 @@ describe("bundled skill: browser", () => {
|
|
|
1829
1829
|
sessionState = new Map<string, string>();
|
|
1830
1830
|
});
|
|
1831
1831
|
|
|
1832
|
-
test("browser skill activation via loaded_skill marker registers all
|
|
1832
|
+
test("browser skill activation via loaded_skill marker registers all browser tools in allowedToolNames", () => {
|
|
1833
1833
|
mockCatalog = [makeSkill("browser", "/path/to/bundled-skills/browser")];
|
|
1834
1834
|
mockManifests = { browser: makeManifest([...BROWSER_TOOL_NAMES]) };
|
|
1835
1835
|
|
|
@@ -1876,7 +1876,7 @@ describe("bundled skill: browser", () => {
|
|
|
1876
1876
|
|
|
1877
1877
|
const tools = mockRegisteredTools.get("browser");
|
|
1878
1878
|
expect(tools).toBeDefined();
|
|
1879
|
-
expect(tools!.length).toBe(
|
|
1879
|
+
expect(tools!.length).toBe(BROWSER_TOOL_NAMES.length);
|
|
1880
1880
|
|
|
1881
1881
|
for (const tool of tools!) {
|
|
1882
1882
|
expect(tool.origin).toBe("skill");
|
|
@@ -2610,10 +2610,13 @@ describe("browser skill migration harness", () => {
|
|
|
2610
2610
|
expect(id1).not.toBe(id2);
|
|
2611
2611
|
});
|
|
2612
2612
|
|
|
2613
|
-
test("BROWSER_TOOL_NAMES contains all
|
|
2614
|
-
expect(BROWSER_TOOL_NAMES).toHaveLength(
|
|
2613
|
+
test("BROWSER_TOOL_NAMES contains all browser tools", () => {
|
|
2614
|
+
expect(BROWSER_TOOL_NAMES).toHaveLength(17);
|
|
2615
2615
|
expect(BROWSER_TOOL_NAMES).toContain("browser_navigate");
|
|
2616
|
+
expect(BROWSER_TOOL_NAMES).toContain("browser_attach");
|
|
2617
|
+
expect(BROWSER_TOOL_NAMES).toContain("browser_detach");
|
|
2616
2618
|
expect(BROWSER_TOOL_NAMES).toContain("browser_fill_credential");
|
|
2619
|
+
expect(BROWSER_TOOL_NAMES).toContain("browser_status");
|
|
2617
2620
|
});
|
|
2618
2621
|
|
|
2619
2622
|
test("assertBrowserToolsPresent passes when all tools present", () => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
classifySlash,
|
|
4
5
|
resolveSlash,
|
|
5
6
|
type SlashContext,
|
|
6
7
|
} from "../daemon/conversation-slash.js";
|
|
@@ -148,3 +149,35 @@ describe("resolveSlash command contract", () => {
|
|
|
148
149
|
expect(result.message).toContain("Pairing is not available");
|
|
149
150
|
});
|
|
150
151
|
});
|
|
152
|
+
|
|
153
|
+
describe("classifySlash is a pure classifier matching resolveSlash kinds", () => {
|
|
154
|
+
// Lookahead in `buildPassthroughBatch` must not run `resolveSlash`'s side
|
|
155
|
+
// effects (/pair registers a pairing request and writes a QR PNG). The
|
|
156
|
+
// pure classifier is synchronous, takes no side-effecting dependencies,
|
|
157
|
+
// and must agree with resolveSlash's `kind`.
|
|
158
|
+
const cases: Array<{ input: string; kind: "passthrough" | "compact" | "unknown" }> = [
|
|
159
|
+
{ input: "/pair", kind: "unknown" },
|
|
160
|
+
{ input: "/models", kind: "unknown" },
|
|
161
|
+
{ input: "/status", kind: "unknown" },
|
|
162
|
+
{ input: "/commands", kind: "unknown" },
|
|
163
|
+
{ input: "/compact", kind: "compact" },
|
|
164
|
+
{ input: "/model", kind: "unknown" },
|
|
165
|
+
{ input: "/model foo", kind: "unknown" },
|
|
166
|
+
{ input: "/opus", kind: "unknown" },
|
|
167
|
+
{ input: "hello", kind: "passthrough" },
|
|
168
|
+
{ input: " /compact ", kind: "compact" },
|
|
169
|
+
{ input: "/pair foo", kind: "passthrough" },
|
|
170
|
+
{ input: "/models foo", kind: "passthrough" },
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
for (const { input, kind } of cases) {
|
|
174
|
+
test(`classifies ${JSON.stringify(input)} as ${kind}`, async () => {
|
|
175
|
+
expect(classifySlash(input)).toBe(kind);
|
|
176
|
+
const resolved = await resolveSlash(
|
|
177
|
+
input,
|
|
178
|
+
makeSlashContext({ userMessageInterface: "macos" }),
|
|
179
|
+
);
|
|
180
|
+
expect(resolved.kind).toBe(kind);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
});
|
|
@@ -90,6 +90,7 @@ mock.module("../security/secret-allowlist.js", () => ({
|
|
|
90
90
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
91
91
|
getConversationType: () => "default",
|
|
92
92
|
setConversationOriginChannelIfUnset: () => {},
|
|
93
|
+
setConversationOriginInterfaceIfUnset: () => {},
|
|
93
94
|
updateConversationContextWindow: () => {},
|
|
94
95
|
deleteMessageById: () => {},
|
|
95
96
|
provenanceFromTrustContext: () => ({
|
|
@@ -113,6 +114,8 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
113
114
|
},
|
|
114
115
|
updateConversationUsage: () => {},
|
|
115
116
|
updateConversationTitle: () => {},
|
|
117
|
+
getMessageById: () => null,
|
|
118
|
+
getLastUserTimestampBefore: () => 0,
|
|
116
119
|
}));
|
|
117
120
|
|
|
118
121
|
mock.module("../memory/conversation-queries.js", () => ({
|
|
@@ -206,6 +209,24 @@ mock.module("../agent/loop.js", () => ({
|
|
|
206
209
|
}
|
|
207
210
|
},
|
|
208
211
|
}));
|
|
212
|
+
// Avoid real workspace-git initialization on /tmp — on CI runners,
|
|
213
|
+
// `git add -A` under /tmp hits permission errors on systemd-private dirs,
|
|
214
|
+
// which blocks `runAgentLoopImpl` for long enough to trip the test's
|
|
215
|
+
// `waitForPendingRun` 2s timeout before `AgentLoop.run` is invoked.
|
|
216
|
+
mock.module("../workspace/git-service.js", () => ({
|
|
217
|
+
getWorkspaceGitService: () => ({
|
|
218
|
+
ensureInitialized: async () => {},
|
|
219
|
+
}),
|
|
220
|
+
}));
|
|
221
|
+
|
|
222
|
+
mock.module("../workspace/turn-commit.js", () => ({
|
|
223
|
+
commitTurnChanges: async () => {},
|
|
224
|
+
}));
|
|
225
|
+
|
|
226
|
+
mock.module("../memory/app-git-service.js", () => ({
|
|
227
|
+
commitAppTurnChanges: async () => {},
|
|
228
|
+
}));
|
|
229
|
+
|
|
209
230
|
mock.module("../memory/canonical-guardian-store.js", () => ({
|
|
210
231
|
listPendingCanonicalGuardianRequestsByDestinationConversation: () => [],
|
|
211
232
|
listCanonicalGuardianRequests: () => [],
|
|
@@ -232,6 +253,12 @@ mock.module("../memory/canonical-guardian-store.js", () => ({
|
|
|
232
253
|
|
|
233
254
|
import { Conversation } from "../daemon/conversation.js";
|
|
234
255
|
|
|
256
|
+
type ConversationWithWorkspaceDeps = Conversation & {
|
|
257
|
+
getWorkspaceGitService?: (_workspaceDir: string) => {
|
|
258
|
+
ensureInitialized: () => Promise<void>;
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
|
|
235
262
|
function makeConversation(): Conversation {
|
|
236
263
|
const provider = {
|
|
237
264
|
name: "mock",
|
|
@@ -244,7 +271,7 @@ function makeConversation(): Conversation {
|
|
|
244
271
|
};
|
|
245
272
|
},
|
|
246
273
|
};
|
|
247
|
-
|
|
274
|
+
const conversation = new Conversation(
|
|
248
275
|
"conv-1",
|
|
249
276
|
provider,
|
|
250
277
|
"system prompt",
|
|
@@ -252,6 +279,13 @@ function makeConversation(): Conversation {
|
|
|
252
279
|
() => {},
|
|
253
280
|
"/tmp",
|
|
254
281
|
);
|
|
282
|
+
// Bypass real workspace git init: with "/tmp" as the workspace dir, a real
|
|
283
|
+
// ensureInitialized() walks all of /tmp and can exceed the 2s waitForPendingRun
|
|
284
|
+
// budget on CI where parallel tests churn /tmp subdirectories.
|
|
285
|
+
(conversation as ConversationWithWorkspaceDeps).getWorkspaceGitService = () => ({
|
|
286
|
+
ensureInitialized: async () => {},
|
|
287
|
+
});
|
|
288
|
+
return conversation;
|
|
255
289
|
}
|
|
256
290
|
|
|
257
291
|
async function waitForPendingRun(
|
|
@@ -296,7 +330,7 @@ describe("Conversation queue — slash-like messages pass through to agent loop"
|
|
|
296
330
|
pendingRuns = [];
|
|
297
331
|
});
|
|
298
332
|
|
|
299
|
-
test("queued slash-like input does not stall queue —
|
|
333
|
+
test("queued slash-like input does not stall queue — batches with siblings", async () => {
|
|
300
334
|
const conversation = makeConversation();
|
|
301
335
|
await conversation.loadFromDb();
|
|
302
336
|
|
|
@@ -313,7 +347,8 @@ describe("Conversation queue — slash-like messages pass through to agent loop"
|
|
|
313
347
|
);
|
|
314
348
|
await waitForPendingRun(1);
|
|
315
349
|
|
|
316
|
-
// Enqueue a slash-like
|
|
350
|
+
// Enqueue a slash-like passthrough and a normal passthrough after it.
|
|
351
|
+
// Both resolve to passthrough, so the batch builder groups them into one run.
|
|
317
352
|
conversation.enqueueMessage(
|
|
318
353
|
"/not-a-skill",
|
|
319
354
|
[],
|
|
@@ -323,30 +358,19 @@ describe("Conversation queue — slash-like messages pass through to agent loop"
|
|
|
323
358
|
conversation.enqueueMessage("msg-3", [], (e) => events3.push(e), "req-3");
|
|
324
359
|
expect(conversation.getQueueDepth()).toBe(2);
|
|
325
360
|
|
|
326
|
-
// Complete first run —
|
|
361
|
+
// Complete first run — drain pulls both queued messages into one batched run.
|
|
327
362
|
resolveRun(0);
|
|
328
363
|
await p1;
|
|
329
|
-
|
|
330
|
-
// The slash-like message should go through agent loop (passthrough)
|
|
331
364
|
await waitForPendingRun(2);
|
|
332
365
|
|
|
333
|
-
//
|
|
366
|
+
// Both batched clients see their own message_dequeued.
|
|
334
367
|
expect(eventsSlash.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
368
|
+
expect(events3.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
335
369
|
|
|
336
|
-
//
|
|
370
|
+
// Exactly 2 runs total: msg-1 + batched [/not-a-skill, msg-3].
|
|
337
371
|
expect(pendingRuns.length).toBe(2);
|
|
338
372
|
|
|
339
|
-
// Complete the slash run so msg-3 can drain
|
|
340
373
|
resolveRun(1);
|
|
341
|
-
await waitForPendingRun(3);
|
|
342
|
-
|
|
343
|
-
// msg-3 events: dequeued (agent loop started)
|
|
344
|
-
expect(events3.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
345
|
-
|
|
346
|
-
// 3 total agent loop runs: msg-1, /not-a-skill, msg-3
|
|
347
|
-
expect(pendingRuns.length).toBe(3);
|
|
348
|
-
|
|
349
|
-
resolveRun(2);
|
|
350
374
|
await new Promise((r) => setTimeout(r, 50));
|
|
351
375
|
});
|
|
352
376
|
|
|
@@ -393,4 +417,51 @@ describe("Conversation queue — slash-like messages pass through to agent loop"
|
|
|
393
417
|
resolveRun(1);
|
|
394
418
|
await new Promise((r) => setTimeout(r, 50));
|
|
395
419
|
});
|
|
420
|
+
|
|
421
|
+
test("passthrough batch is terminated by a /compact in the middle of the queue", async () => {
|
|
422
|
+
const conversation = makeConversation();
|
|
423
|
+
await conversation.loadFromDb();
|
|
424
|
+
|
|
425
|
+
const eventsHi: ServerMessage[] = [];
|
|
426
|
+
const eventsCompact: ServerMessage[] = [];
|
|
427
|
+
const eventsBye: ServerMessage[] = [];
|
|
428
|
+
|
|
429
|
+
// Start in-flight message
|
|
430
|
+
const p1 = conversation.processMessage("msg-1", [], () => {}, "req-1");
|
|
431
|
+
await waitForPendingRun(1);
|
|
432
|
+
|
|
433
|
+
// Enqueue ["hi", "/compact", "bye"]. /compact is non-passthrough, so the
|
|
434
|
+
// batch builder stops at "hi" (length-1 batch → drainSingleMessage). Then
|
|
435
|
+
// /compact takes its short-circuit path (no new runAgentLoop), and "bye"
|
|
436
|
+
// drains as its own run.
|
|
437
|
+
conversation.enqueueMessage("hi", [], (e) => eventsHi.push(e), "req-hi");
|
|
438
|
+
conversation.enqueueMessage(
|
|
439
|
+
"/compact",
|
|
440
|
+
[],
|
|
441
|
+
(e) => eventsCompact.push(e),
|
|
442
|
+
"req-compact",
|
|
443
|
+
);
|
|
444
|
+
conversation.enqueueMessage("bye", [], (e) => eventsBye.push(e), "req-bye");
|
|
445
|
+
expect(conversation.getQueueDepth()).toBe(3);
|
|
446
|
+
|
|
447
|
+
// Resolve msg-1 → drain pulls only "hi" (batch builder stops at /compact).
|
|
448
|
+
resolveRun(0);
|
|
449
|
+
await p1;
|
|
450
|
+
await waitForPendingRun(2);
|
|
451
|
+
|
|
452
|
+
expect(pendingRuns.length).toBe(2);
|
|
453
|
+
expect(eventsHi.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
454
|
+
|
|
455
|
+
// Resolve "hi" → /compact short-circuits without a new runAgentLoop, then
|
|
456
|
+
// drains "bye" as its own run.
|
|
457
|
+
resolveRun(1);
|
|
458
|
+
await waitForPendingRun(3);
|
|
459
|
+
|
|
460
|
+
expect(eventsCompact.some((e) => e.type === "message_complete")).toBe(true);
|
|
461
|
+
expect(eventsBye.some((e) => e.type === "message_dequeued")).toBe(true);
|
|
462
|
+
expect(pendingRuns.length).toBe(3);
|
|
463
|
+
|
|
464
|
+
resolveRun(2);
|
|
465
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
466
|
+
});
|
|
396
467
|
});
|
|
@@ -120,6 +120,8 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
120
120
|
},
|
|
121
121
|
updateConversationUsage: () => {},
|
|
122
122
|
updateConversationTitle: () => {},
|
|
123
|
+
getMessageById: () => null,
|
|
124
|
+
getLastUserTimestampBefore: () => 0,
|
|
123
125
|
}));
|
|
124
126
|
|
|
125
127
|
mock.module("../memory/conversation-queries.js", () => ({
|