@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e
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 +1 -1
- package/Dockerfile +8 -0
- package/docs/activation-funnel-telemetry.md +13 -7
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/openapi.yaml +530 -107
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +8 -0
- package/src/__tests__/activation-early-marking.test.ts +6 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
- package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
- package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
- package/src/__tests__/app-bundler.test.ts +4 -12
- package/src/__tests__/app-executors.test.ts +2 -49
- package/src/__tests__/app-routes-csp.test.ts +114 -146
- package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
- package/src/__tests__/build-persisted-content.test.ts +96 -0
- package/src/__tests__/bundle-scanner.test.ts +27 -1
- package/src/__tests__/call-controller.test.ts +291 -0
- package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
- package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
- package/src/__tests__/config-loader-backfill.test.ts +9 -4
- package/src/__tests__/config-schema-cmd.test.ts +10 -11
- package/src/__tests__/config-schema.test.ts +190 -257
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
- package/src/__tests__/conversation-agent-loop.test.ts +22 -9
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
- package/src/__tests__/conversation-process-callsite.test.ts +12 -19
- package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
- package/src/__tests__/conversation-summarize-route.test.ts +36 -44
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
- package/src/__tests__/conversation-usage.test.ts +4 -12
- package/src/__tests__/credential-routes.test.ts +250 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/db-migration-rollback.test.ts +22 -0
- package/src/__tests__/empty-response-hook.test.ts +186 -1
- package/src/__tests__/external-plugin-loader.test.ts +11 -5
- package/src/__tests__/heartbeat-service.test.ts +0 -28
- package/src/__tests__/host-shell-tool.test.ts +2 -0
- package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
- package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
- package/src/__tests__/invite-routes-http.test.ts +12 -16
- package/src/__tests__/list-all-apps.test.ts +0 -4
- package/src/__tests__/llm-context-resolution.test.ts +32 -56
- package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
- package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
- package/src/__tests__/llm-resolver.test.ts +342 -602
- package/src/__tests__/llm-schema.test.ts +79 -37
- package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
- package/src/__tests__/media-stream-output.test.ts +259 -3
- package/src/__tests__/media-stream-server-integration.test.ts +22 -1
- package/src/__tests__/media-stream-stt-session.test.ts +47 -0
- package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
- package/src/__tests__/memory-recall-log-store.test.ts +47 -13
- package/src/__tests__/mock-gateway-ipc.ts +46 -1
- package/src/__tests__/mtime-cache.test.ts +61 -0
- package/src/__tests__/navigate-settings-tab.test.ts +2 -0
- package/src/__tests__/normalize-onboarding.test.ts +33 -0
- package/src/__tests__/onboarding-persona-write.test.ts +26 -0
- package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
- package/src/__tests__/provider-connections-backfill.test.ts +138 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
- package/src/__tests__/provider-registry-ollama.test.ts +8 -18
- package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
- package/src/__tests__/provider-usage-tracking.test.ts +9 -19
- package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
- package/src/__tests__/published-app-updater.test.ts +22 -16
- package/src/__tests__/registry.test.ts +5 -16
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
- package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
- package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
- package/src/__tests__/reveal-success-registry.test.ts +123 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
- package/src/__tests__/secret-fixtures.ts +9 -0
- package/src/__tests__/server-history-render.test.ts +28 -0
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/slack-share-routes.test.ts +0 -1
- package/src/__tests__/stt-stream-session.test.ts +6 -5
- package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
- package/src/__tests__/subagent-disposal.test.ts +2 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
- package/src/__tests__/subagent-manager-notify.test.ts +2 -0
- package/src/__tests__/subagent-role-registry.test.ts +37 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
- package/src/__tests__/subagent-terminal-message.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
- package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
- package/src/__tests__/telemetry-routes.test.ts +99 -19
- package/src/__tests__/tool-audit.test.ts +34 -4
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-profiler.test.ts +72 -1
- package/src/__tests__/tool-result-spool.test.ts +49 -4
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
- package/src/__tests__/ui-channel-variants.test.ts +108 -0
- package/src/__tests__/ui-shape-teaching.test.ts +255 -0
- package/src/__tests__/usage-attribution.test.ts +18 -41
- package/src/__tests__/user-plugin-loader.test.ts +4 -4
- package/src/__tests__/voice-config-update.test.ts +46 -0
- package/src/__tests__/voice-session-bridge.test.ts +216 -84
- package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
- package/src/agent/loop.ts +4 -3
- package/src/api/events/open-conversation.test.ts +64 -0
- package/src/api/events/open-conversation.ts +33 -0
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +25 -35
- package/src/bundler/app-bundler.ts +34 -48
- package/src/bundler/app-compiler.ts +39 -4
- package/src/bundler/bundle-scanner.ts +13 -0
- package/src/bundler/manifest.ts +1 -1
- package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
- package/src/calls/call-constants.ts +5 -0
- package/src/calls/call-controller.ts +100 -32
- package/src/calls/call-transport.ts +9 -0
- package/src/calls/media-stream-output.ts +107 -4
- package/src/calls/media-stream-server.ts +29 -9
- package/src/calls/media-stream-stt-session.ts +7 -1
- package/src/calls/media-turn-detector.ts +11 -1
- package/src/calls/voice-session-bridge.ts +84 -63
- package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
- package/src/cli/commands/__tests__/notifications.test.ts +24 -3
- package/src/cli/commands/config.help.ts +6 -6
- package/src/cli/commands/credentials.help.ts +13 -0
- package/src/cli/commands/credentials.ts +6 -1
- package/src/cli/commands/email.help.ts +7 -0
- package/src/cli/commands/email.ts +35 -1
- package/src/cli/commands/inference-providers.ts +168 -107
- package/src/cli/commands/inference.help.ts +118 -46
- package/src/cli/commands/memory/index.help.ts +23 -0
- package/src/cli/commands/memory/nodes.ts +146 -0
- package/src/cli/commands/notifications.help.ts +10 -10
- package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
- package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
- package/src/cli/commands/oauth/connect.test.ts +126 -0
- package/src/cli/commands/oauth/connect.ts +29 -6
- package/src/cli/commands/oauth/index.help.ts +7 -1
- package/src/cli/commands/oauth/status.test.ts +69 -3
- package/src/cli/commands/oauth/status.ts +50 -12
- package/src/cli/commands/plugins.help.ts +13 -2
- package/src/cli/commands/plugins.ts +61 -7
- package/src/cli/commands/telemetry.help.ts +13 -0
- package/src/cli/commands/telemetry.ts +45 -5
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
- package/src/cli/lib/bundled-marketplace.json +93 -36
- package/src/cli/lib/inspect-plugin.ts +5 -1
- package/src/cli/lib/install-from-github.ts +68 -4
- package/src/cli/lib/plugin-catalog-local.ts +6 -2
- package/src/cli/lib/plugin-fingerprint.ts +3 -3
- package/src/cli/lib/upgrade-plugin.ts +236 -35
- package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
- package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
- package/src/config/__tests__/profile-materialization.test.ts +1 -88
- package/src/config/bundled-skills/AGENTS.md +3 -30
- package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
- package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
- package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +3 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
- package/src/config/call-site-defaults.ts +3 -3
- package/src/config/feature-flag-registry.json +24 -39
- package/src/config/llm-resolver.ts +88 -475
- package/src/config/profile-materialization.ts +11 -11
- package/src/config/schema.ts +93 -127
- package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
- package/src/config/schemas/__tests__/stt.test.ts +31 -3
- package/src/config/schemas/live-voice.ts +10 -4
- package/src/config/schemas/llm.ts +52 -15
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +8 -0
- package/src/config/schemas/memory-v2.ts +2 -2
- package/src/config/schemas/memory-v3.ts +1 -1
- package/src/config/schemas/services.ts +6 -3
- package/src/config/schemas/stt.ts +38 -21
- package/src/config/schemas/tts.ts +13 -18
- package/src/config/skills.ts +31 -21
- package/src/context/compactor.ts +44 -7
- package/src/context/post-turn-tool-result-truncation.ts +4 -2
- package/src/context/tool-result-spool.ts +26 -5
- package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
- package/src/conversations/message-consolidation.ts +22 -2
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
- package/src/daemon/app-source-watcher.ts +17 -23
- package/src/daemon/chat-credential-redaction.ts +1365 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +627 -34
- package/src/daemon/conversation-agent-loop.ts +71 -31
- package/src/daemon/conversation-error.ts +36 -14
- package/src/daemon/conversation-process.ts +22 -0
- package/src/daemon/conversation-store.ts +35 -0
- package/src/daemon/conversation-surfaces.ts +25 -25
- package/src/daemon/conversation-tool-setup.ts +33 -7
- package/src/daemon/conversation.ts +55 -5
- package/src/daemon/handlers/shared.ts +33 -2
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/message-types/conversations.ts +6 -17
- package/src/daemon/providers-setup.ts +8 -0
- package/src/daemon/tool-setup-types.ts +7 -1
- package/src/daemon/wake-conversation-ops.ts +10 -1
- package/src/hooks/types.ts +9 -0
- package/src/ipc/__tests__/email-ipc.test.ts +90 -0
- package/src/ipc/gateway-client.test.ts +59 -0
- package/src/ipc/gateway-client.ts +70 -27
- package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
- package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
- package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
- package/src/live-voice/__tests__/protocol.test.ts +122 -0
- package/src/live-voice/live-voice-session.ts +443 -40
- package/src/live-voice/protocol.ts +143 -1
- package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
- package/src/monitoring/plugin-source-watch.ts +3 -62
- package/src/notifications/README.md +1 -1
- package/src/permissions/checker.ts +8 -4
- package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
- package/src/persistence/conversation-crud.ts +104 -2
- package/src/persistence/db-init.ts +14 -3
- package/src/persistence/job-handlers/cleanup.ts +15 -7
- package/src/persistence/llm-request-log-store.ts +88 -52
- package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
- package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
- package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
- package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
- package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
- package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
- package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
- package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
- package/src/persistence/migrations/helpers/relocation.ts +44 -1
- package/src/persistence/migrations/run-migrations.ts +25 -1
- package/src/persistence/schema/infrastructure.ts +9 -0
- package/src/persistence/schema/memory-core.ts +3 -0
- package/src/persistence/schema/memory-injection.ts +2 -0
- package/src/persistence/steps.ts +36 -0
- package/src/platform/client.test.ts +1 -44
- package/src/platform/client.ts +10 -20
- package/src/platform/consent-cache.test.ts +89 -23
- package/src/platform/consent-cache.ts +71 -33
- package/src/plugin-api/constants.ts +12 -0
- package/src/plugin-api/conversation-turn.ts +37 -14
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/resolve-credential.ts +75 -0
- package/src/plugin-api/vision-support.test.ts +8 -19
- package/src/plugin-api/vision-support.ts +26 -17
- package/src/plugins/collect-source-versions.ts +77 -0
- package/src/plugins/defaults/compaction/compact.ts +6 -0
- package/src/plugins/defaults/compaction/window-manager.ts +9 -0
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
- package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
- package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
- package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
- package/src/plugins/defaults/index.ts +8 -1
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
- package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
- package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
- package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
- package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
- package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
- package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
- package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
- package/src/plugins/defaults/memory/graph/store.ts +114 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
- package/src/plugins/defaults/memory/graph/tools.ts +47 -13
- package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
- package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
- package/src/plugins/defaults/memory/memory-db.ts +3 -2
- package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
- package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
- package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
- package/src/plugins/defaults/memory/path-containment.ts +21 -0
- package/src/plugins/defaults/memory/prompt-override.ts +51 -13
- package/src/plugins/defaults/memory/tools.test.ts +34 -0
- package/src/plugins/defaults/memory/tools.ts +12 -2
- package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
- package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
- package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
- package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
- package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
- package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
- package/src/plugins/defaults/memory/v2/migration.ts +46 -16
- package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
- package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
- package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
- package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
- package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
- package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
- package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
- package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
- package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
- package/src/plugins/defaults/memory/v3/prune.ts +17 -11
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
- package/src/plugins/external-plugin-loader.ts +15 -15
- package/src/plugins/mtime-cache.ts +47 -0
- package/src/plugins/pipeline.ts +9 -1
- package/src/plugins/plugin-execution-context.ts +44 -0
- package/src/plugins/plugin-tree-walk.ts +31 -24
- package/src/plugins/source-fingerprint.ts +3 -4
- package/src/prompts/normalize-onboarding.ts +12 -0
- package/src/prompts/persona-resolver.ts +8 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
- package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
- package/src/providers/__tests__/retry-callsite.test.ts +235 -215
- package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
- package/src/providers/atlascloud/client.ts +10 -49
- package/src/providers/baseten/client.ts +43 -0
- package/src/providers/call-site-routing.ts +26 -13
- package/src/providers/connection-resolution.ts +9 -11
- package/src/providers/fetch-provider-catalog.ts +4 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
- package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
- package/src/providers/inference/adapter-factory.ts +27 -6
- package/src/providers/inference/auth.ts +29 -1
- package/src/providers/inference/backfill.ts +61 -10
- package/src/providers/inference/connection-availability.ts +3 -2
- package/src/providers/inference/resolve-auth.ts +9 -1
- package/src/providers/model-catalog.ts +37 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
- package/src/providers/openai/api-key-validation.ts +70 -0
- package/src/providers/retry.ts +2 -0
- package/src/providers/types.ts +11 -4
- package/src/providers/vellum-model-routing.test.ts +26 -0
- package/src/providers/vellum-model-routing.ts +30 -0
- package/src/providers/voice-error-copy.ts +47 -0
- package/src/runtime/agent-wake.ts +40 -24
- package/src/runtime/for-chat-mint-registry.ts +118 -0
- package/src/runtime/reveal-nonce.ts +49 -0
- package/src/runtime/reveal-success-registry.ts +306 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
- package/src/runtime/routes/app-management-routes.ts +152 -45
- package/src/runtime/routes/app-routes.ts +16 -77
- package/src/runtime/routes/canned-message-complete.ts +16 -16
- package/src/runtime/routes/conversation-management-routes.ts +17 -14
- package/src/runtime/routes/conversation-query-routes.ts +54 -6
- package/src/runtime/routes/conversation-routes.ts +48 -2
- package/src/runtime/routes/credential-routes.ts +116 -3
- package/src/runtime/routes/email-routes.ts +17 -1
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
- package/src/runtime/routes/inference-send-routes.ts +1 -1
- package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
- package/src/runtime/routes/plugins-routes.ts +37 -6
- package/src/runtime/routes/publish-routes.ts +15 -18
- package/src/runtime/routes/schedule-worker-routes.ts +9 -0
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/telemetry-routes.ts +149 -45
- package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
- package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
- package/src/schedule/schedule-store.ts +12 -1
- package/src/schedule/schedule-timezone.ts +63 -0
- package/src/schedule/scheduler.ts +100 -6
- package/src/schedule/worker-control.ts +19 -0
- package/src/security/auth-fallback-events-store.ts +7 -6
- package/src/security/secret-scanner.ts +26 -1
- package/src/services/published-app-updater.ts +6 -11
- package/src/stt/stt-stream-session.ts +36 -16
- package/src/subagent/manager.ts +34 -8
- package/src/telemetry/AGENTS.md +30 -1
- package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
- package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
- package/src/telemetry/config-setting-snapshot.ts +44 -10
- package/src/telemetry/telemetry-event-sources.test.ts +124 -30
- package/src/telemetry/telemetry-event-sources.ts +137 -83
- package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
- package/src/telemetry/telemetry-events-outbox.ts +60 -9
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire-validation.ts +39 -2
- package/src/telemetry/telemetry-wire.generated.ts +8 -0
- package/src/telemetry/tool-audit.ts +15 -9
- package/src/telemetry/tool-executed-events-store.test.ts +1 -1
- package/src/telemetry/turn-events-store.ts +20 -0
- package/src/telemetry/types.ts +45 -12
- package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
- package/src/telemetry/usage-telemetry-reporter.ts +117 -18
- package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
- package/src/telemetry/watchdog-direct-emit.ts +11 -6
- package/src/tools/apps/executors.ts +11 -36
- package/src/tools/executor.ts +14 -1
- package/src/tools/host-terminal/host-shell.ts +6 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
- package/src/tools/network/web-fetch.ts +6 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/subagent/spawn.ts +7 -11
- package/src/tools/terminal/shell.ts +5 -2
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/tool-profiler.ts +37 -6
- package/src/tools/ui-surface/channel-variants.ts +101 -0
- package/src/tools/ui-surface/definitions.ts +23 -67
- package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
- package/src/tts/__tests__/provider-adapters.test.ts +18 -0
- package/src/tts/provider-catalog.ts +2 -4
- package/src/tts/providers/deepgram-provider.ts +10 -1
- package/src/types/onboarding-context.ts +8 -0
- package/src/usage/attribution.ts +18 -112
- package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
- package/src/util/provider-error-patterns.ts +7 -1
- package/src/util/worker-process.ts +105 -0
- package/src/watcher/__tests__/telemetry.test.ts +17 -4
- package/src/watcher/telemetry.ts +5 -5
- package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/workspace/provider-commit-message-generator.ts +6 -4
- package/src/__tests__/app-open-proxy.test.ts +0 -67
- package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
- package/src/onboarding/onboarding-research-events-store.ts +0 -104
- package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
- package/src/tools/apps/definitions.ts +0 -73
- package/src/tools/apps/open-proxy.ts +0 -43
|
@@ -54,12 +54,14 @@ mock.module("../security/credential-key.js", () => ({
|
|
|
54
54
|
let mockConsultationTimeoutMs = 90_000;
|
|
55
55
|
let mockSilenceTimeoutMs = 30_000;
|
|
56
56
|
let mockEndCallListenWindowMs = 0;
|
|
57
|
+
let mockEndCallDrainMaxWaitMs = 15_000;
|
|
57
58
|
|
|
58
59
|
mock.module("../calls/call-constants.js", () => ({
|
|
59
60
|
getMaxCallDurationMs: () => 12 * 60 * 1000,
|
|
60
61
|
getUserConsultationTimeoutMs: () => mockConsultationTimeoutMs,
|
|
61
62
|
getSilenceTimeoutMs: () => mockSilenceTimeoutMs,
|
|
62
63
|
getEndCallListenWindowMs: () => mockEndCallListenWindowMs,
|
|
64
|
+
getEndCallDrainMaxWaitMs: () => mockEndCallDrainMaxWaitMs,
|
|
63
65
|
}));
|
|
64
66
|
|
|
65
67
|
// ── Voice session bridge mock ────────────────────────────────────────
|
|
@@ -246,6 +248,7 @@ interface MockTransport extends CallTransport {
|
|
|
246
248
|
sentPlayUrls: string[];
|
|
247
249
|
endCalled: boolean;
|
|
248
250
|
endReason: string | undefined;
|
|
251
|
+
cancelPendingSpeechCount: number;
|
|
249
252
|
}
|
|
250
253
|
|
|
251
254
|
function createMockTransport(): MockTransport {
|
|
@@ -254,6 +257,7 @@ function createMockTransport(): MockTransport {
|
|
|
254
257
|
sentPlayUrls: [] as string[],
|
|
255
258
|
_endCalled: false,
|
|
256
259
|
_endReason: undefined as string | undefined,
|
|
260
|
+
_cancelPendingSpeechCount: 0,
|
|
257
261
|
};
|
|
258
262
|
|
|
259
263
|
return {
|
|
@@ -269,6 +273,9 @@ function createMockTransport(): MockTransport {
|
|
|
269
273
|
get endReason() {
|
|
270
274
|
return state._endReason;
|
|
271
275
|
},
|
|
276
|
+
get cancelPendingSpeechCount() {
|
|
277
|
+
return state._cancelPendingSpeechCount;
|
|
278
|
+
},
|
|
272
279
|
sendTextToken(token: string, last: boolean) {
|
|
273
280
|
state.sentTokens.push({ token, last });
|
|
274
281
|
},
|
|
@@ -279,6 +286,9 @@ function createMockTransport(): MockTransport {
|
|
|
279
286
|
state._endCalled = true;
|
|
280
287
|
state._endReason = reason;
|
|
281
288
|
},
|
|
289
|
+
cancelPendingSpeech() {
|
|
290
|
+
state._cancelPendingSpeechCount++;
|
|
291
|
+
},
|
|
282
292
|
} as MockTransport;
|
|
283
293
|
}
|
|
284
294
|
|
|
@@ -365,6 +375,8 @@ function setupController(
|
|
|
365
375
|
trustContext?: import("../daemon/trust-context-types.js").TrustContext;
|
|
366
376
|
/** Simulate the media-stream transport's PCM requirement. */
|
|
367
377
|
requiresPcmAudio?: boolean;
|
|
378
|
+
/** Simulate a transport that gates teardown on playback drain. */
|
|
379
|
+
awaitPlaybackDrained?: () => Promise<void>;
|
|
368
380
|
},
|
|
369
381
|
) {
|
|
370
382
|
ensureConversation("conv-ctrl-test");
|
|
@@ -380,6 +392,9 @@ function setupController(
|
|
|
380
392
|
if (opts?.requiresPcmAudio) {
|
|
381
393
|
Object.assign(transport, { requiresPcmAudio: true });
|
|
382
394
|
}
|
|
395
|
+
if (opts?.awaitPlaybackDrained) {
|
|
396
|
+
Object.assign(transport, { awaitPlaybackDrained: opts.awaitPlaybackDrained });
|
|
397
|
+
}
|
|
383
398
|
const controller = new CallController(session.id, transport, task ?? null, {
|
|
384
399
|
assistantId: opts?.assistantId,
|
|
385
400
|
trustContext: opts?.trustContext,
|
|
@@ -440,6 +455,7 @@ describe("call-controller", () => {
|
|
|
440
455
|
mockConsultationTimeoutMs = 90_000;
|
|
441
456
|
mockSilenceTimeoutMs = 30_000;
|
|
442
457
|
mockEndCallListenWindowMs = 0;
|
|
458
|
+
mockEndCallDrainMaxWaitMs = 15_000;
|
|
443
459
|
// Reset TTS config to defaults so per-test mutations don't leak.
|
|
444
460
|
const cfg = loadConfig();
|
|
445
461
|
cfg.services.tts.provider = "elevenlabs";
|
|
@@ -897,6 +913,281 @@ describe("call-controller", () => {
|
|
|
897
913
|
controller.destroy();
|
|
898
914
|
});
|
|
899
915
|
|
|
916
|
+
// ── END_CALL playback-drain gating ───────────────────────────────
|
|
917
|
+
|
|
918
|
+
test("END_CALL does not end the session until playback drain resolves", async () => {
|
|
919
|
+
mockEndCallListenWindowMs = 0;
|
|
920
|
+
let resolveDrain!: () => void;
|
|
921
|
+
const drainPromise = new Promise<void>((r) => {
|
|
922
|
+
resolveDrain = r;
|
|
923
|
+
});
|
|
924
|
+
mockStartVoiceTurn.mockImplementation(
|
|
925
|
+
createMockVoiceTurn(["Thank you for calling, goodbye! ", "[END_CALL]"]),
|
|
926
|
+
);
|
|
927
|
+
const { session, relay, controller } = setupController(undefined, {
|
|
928
|
+
awaitPlaybackDrained: () => drainPromise,
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
await controller.handleCallerUtterance("That is all, thanks");
|
|
932
|
+
|
|
933
|
+
// Drain has not resolved — teardown must be blocked.
|
|
934
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
935
|
+
expect(relay.endCalled).toBe(false);
|
|
936
|
+
expect(getCallSession(session.id)!.status).toBe("in_progress");
|
|
937
|
+
|
|
938
|
+
// Drain completes — teardown proceeds and ends the session.
|
|
939
|
+
resolveDrain();
|
|
940
|
+
await pollUntil(() => relay.endCalled);
|
|
941
|
+
expect(getCallSession(session.id)!.status).toBe("completed");
|
|
942
|
+
|
|
943
|
+
controller.destroy();
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
test("END_CALL honors the listen window after drain resolves", async () => {
|
|
947
|
+
mockEndCallListenWindowMs = 30;
|
|
948
|
+
let resolveDrain!: () => void;
|
|
949
|
+
const drainPromise = new Promise<void>((r) => {
|
|
950
|
+
resolveDrain = r;
|
|
951
|
+
});
|
|
952
|
+
mockStartVoiceTurn.mockImplementation(
|
|
953
|
+
createMockVoiceTurn(["Goodbye! ", "[END_CALL]"]),
|
|
954
|
+
);
|
|
955
|
+
const { session, relay, controller } = setupController(undefined, {
|
|
956
|
+
awaitPlaybackDrained: () => drainPromise,
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
await controller.handleCallerUtterance("That is all, thanks");
|
|
960
|
+
resolveDrain();
|
|
961
|
+
|
|
962
|
+
// Listen window still gates completion after drain.
|
|
963
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
964
|
+
expect(relay.endCalled).toBe(false);
|
|
965
|
+
|
|
966
|
+
await pollUntil(() => relay.endCalled);
|
|
967
|
+
expect(getCallSession(session.id)!.status).toBe("completed");
|
|
968
|
+
|
|
969
|
+
controller.destroy();
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
test("END_CALL forces hangup after the drain safety cap when drain never resolves", async () => {
|
|
973
|
+
mockEndCallListenWindowMs = 0;
|
|
974
|
+
mockEndCallDrainMaxWaitMs = 25;
|
|
975
|
+
mockStartVoiceTurn.mockImplementation(
|
|
976
|
+
createMockVoiceTurn(["Goodbye! ", "[END_CALL]"]),
|
|
977
|
+
);
|
|
978
|
+
// Drain that never resolves — only the safety cap can release teardown.
|
|
979
|
+
const { session, relay, controller } = setupController(undefined, {
|
|
980
|
+
awaitPlaybackDrained: () => new Promise<void>(() => {}),
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
await controller.handleCallerUtterance("That is all, thanks");
|
|
984
|
+
expect(relay.endCalled).toBe(false);
|
|
985
|
+
|
|
986
|
+
await pollUntil(() => relay.endCalled);
|
|
987
|
+
expect(getCallSession(session.id)!.status).toBe("completed");
|
|
988
|
+
|
|
989
|
+
controller.destroy();
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
test("a second END_CALL teardown keeps its own safety cap after cancelling the first", async () => {
|
|
993
|
+
// Cancelling a mid-drain teardown then immediately starting another must
|
|
994
|
+
// not let the old teardown's continuation clear the new one's cap.
|
|
995
|
+
mockEndCallListenWindowMs = 30;
|
|
996
|
+
mockEndCallDrainMaxWaitMs = 40;
|
|
997
|
+
const turnContents: string[] = [];
|
|
998
|
+
mockStartVoiceTurn.mockImplementation(
|
|
999
|
+
async (opts: {
|
|
1000
|
+
content: string;
|
|
1001
|
+
onTextDelta: (t: string) => void;
|
|
1002
|
+
onComplete: () => void;
|
|
1003
|
+
}) => {
|
|
1004
|
+
turnContents.push(opts.content);
|
|
1005
|
+
opts.onTextDelta(
|
|
1006
|
+
turnContents.length === 2 ? "Okay, bye. [END_CALL]" : "Goodbye! [END_CALL]",
|
|
1007
|
+
);
|
|
1008
|
+
opts.onComplete();
|
|
1009
|
+
return { turnId: `run-${turnContents.length}`, abort: () => {} };
|
|
1010
|
+
},
|
|
1011
|
+
);
|
|
1012
|
+
// Both goodbyes' drains never echo — only each teardown's own safety cap
|
|
1013
|
+
// can release it.
|
|
1014
|
+
const { session, relay, controller } = setupController(undefined, {
|
|
1015
|
+
awaitPlaybackDrained: () => new Promise<void>(() => {}),
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
await controller.handleCallerUtterance("That is all"); // teardown A (mid-drain)
|
|
1019
|
+
// Re-engage: cancels A and starts turn 2, which emits END_CALL → teardown B.
|
|
1020
|
+
await controller.handleCallerUtterance("Actually, bye");
|
|
1021
|
+
|
|
1022
|
+
// B must still hang up via its own cap despite A's cancelled continuation.
|
|
1023
|
+
await pollUntil(() => relay.endCalled);
|
|
1024
|
+
expect(getCallSession(session.id)!.status).toBe("completed");
|
|
1025
|
+
|
|
1026
|
+
controller.destroy();
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
test("caller re-engagement during the drain phase cancels teardown and defers", async () => {
|
|
1030
|
+
mockEndCallListenWindowMs = 30;
|
|
1031
|
+
const turnContents: string[] = [];
|
|
1032
|
+
mockStartVoiceTurn.mockImplementation(
|
|
1033
|
+
async (opts: {
|
|
1034
|
+
content: string;
|
|
1035
|
+
onTextDelta: (t: string) => void;
|
|
1036
|
+
onComplete: () => void;
|
|
1037
|
+
}) => {
|
|
1038
|
+
turnContents.push(opts.content);
|
|
1039
|
+
if (turnContents.length === 1) {
|
|
1040
|
+
opts.onTextDelta("Goodbye! [END_CALL]");
|
|
1041
|
+
} else {
|
|
1042
|
+
opts.onTextDelta("Sure, I'm still here.");
|
|
1043
|
+
}
|
|
1044
|
+
opts.onComplete();
|
|
1045
|
+
return { turnId: `run-${turnContents.length}`, abort: () => {} };
|
|
1046
|
+
},
|
|
1047
|
+
);
|
|
1048
|
+
// Drain never resolves during the test window; re-engagement must still
|
|
1049
|
+
// cancel the pending teardown mid-drain.
|
|
1050
|
+
const { session, relay, controller } = setupController(undefined, {
|
|
1051
|
+
awaitPlaybackDrained: () => new Promise<void>(() => {}),
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
await controller.handleCallerUtterance("That is all, thanks");
|
|
1055
|
+
expect(relay.endCalled).toBe(false);
|
|
1056
|
+
|
|
1057
|
+
// Caller re-engages while teardown is still waiting on drain.
|
|
1058
|
+
await controller.handleCallerUtterance("Wait, one more thing");
|
|
1059
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1060
|
+
|
|
1061
|
+
expect(relay.endCalled).toBe(false);
|
|
1062
|
+
expect(getCallSession(session.id)!.status).toBe("in_progress");
|
|
1063
|
+
expect(turnContents).toContain("Wait, one more thing");
|
|
1064
|
+
const allText = relay.sentTokens.map((t) => t.token).join("");
|
|
1065
|
+
expect(allText).toContain("I'm still here.");
|
|
1066
|
+
// The abandoned goodbye's queued speech must be cancelled so it can't
|
|
1067
|
+
// play over the caller's follow-up.
|
|
1068
|
+
expect(relay.cancelPendingSpeechCount).toBeGreaterThan(0);
|
|
1069
|
+
|
|
1070
|
+
controller.destroy();
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
test("repeat END_CALL still waits for drain but skips the listen window", async () => {
|
|
1074
|
+
mockEndCallListenWindowMs = 30;
|
|
1075
|
+
let resolveSecondDrain!: () => void;
|
|
1076
|
+
let drainCalls = 0;
|
|
1077
|
+
const turnContents: string[] = [];
|
|
1078
|
+
mockStartVoiceTurn.mockImplementation(
|
|
1079
|
+
async (opts: {
|
|
1080
|
+
content: string;
|
|
1081
|
+
onTextDelta: (t: string) => void;
|
|
1082
|
+
onComplete: () => void;
|
|
1083
|
+
}) => {
|
|
1084
|
+
turnContents.push(opts.content);
|
|
1085
|
+
if (turnContents.length === 1) {
|
|
1086
|
+
opts.onTextDelta("Goodbye! [END_CALL]");
|
|
1087
|
+
} else if (turnContents.length === 2) {
|
|
1088
|
+
opts.onTextDelta("Okay, one quick thing.");
|
|
1089
|
+
} else {
|
|
1090
|
+
opts.onTextDelta("I have to go now. Goodbye! [END_CALL]");
|
|
1091
|
+
}
|
|
1092
|
+
opts.onComplete();
|
|
1093
|
+
return { turnId: `run-${turnContents.length}`, abort: () => {} };
|
|
1094
|
+
},
|
|
1095
|
+
);
|
|
1096
|
+
const { session, relay, controller } = setupController(undefined, {
|
|
1097
|
+
awaitPlaybackDrained: () => {
|
|
1098
|
+
drainCalls++;
|
|
1099
|
+
// First END_CALL drain resolves immediately; the second one is held
|
|
1100
|
+
// so we can assert teardown is gated on it, not on a 0ms timer.
|
|
1101
|
+
if (drainCalls === 1) return Promise.resolve();
|
|
1102
|
+
return new Promise<void>((r) => {
|
|
1103
|
+
resolveSecondDrain = r;
|
|
1104
|
+
});
|
|
1105
|
+
},
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
// First END_CALL — listen window is armed; caller re-engages (deferral #1).
|
|
1109
|
+
await controller.handleCallerUtterance("That is all, thanks");
|
|
1110
|
+
await controller.handleCallerUtterance("Wait, one more thing");
|
|
1111
|
+
await new Promise((r) => setTimeout(r, 40));
|
|
1112
|
+
expect(relay.endCalled).toBe(false);
|
|
1113
|
+
|
|
1114
|
+
// Second END_CALL after the deferral — must still block on drain.
|
|
1115
|
+
await controller.handleCallerUtterance("Just kidding, keep going");
|
|
1116
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
1117
|
+
expect(relay.endCalled).toBe(false);
|
|
1118
|
+
|
|
1119
|
+
// Drain resolves — teardown completes without any listen window.
|
|
1120
|
+
resolveSecondDrain();
|
|
1121
|
+
await pollUntil(() => relay.endCalled);
|
|
1122
|
+
expect(getCallSession(session.id)!.status).toBe("completed");
|
|
1123
|
+
|
|
1124
|
+
controller.destroy();
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
test("transport without awaitPlaybackDrained ends after listen window as before", async () => {
|
|
1128
|
+
mockEndCallListenWindowMs = 25;
|
|
1129
|
+
mockStartVoiceTurn.mockImplementation(
|
|
1130
|
+
createMockVoiceTurn(["Goodbye! ", "[END_CALL]"]),
|
|
1131
|
+
);
|
|
1132
|
+
// Default mock transport does not implement awaitPlaybackDrained.
|
|
1133
|
+
const { session, relay, controller } = setupController();
|
|
1134
|
+
|
|
1135
|
+
await controller.handleCallerUtterance("That is all, thanks");
|
|
1136
|
+
expect(relay.endCalled).toBe(false);
|
|
1137
|
+
|
|
1138
|
+
await pollUntil(() => relay.endCalled);
|
|
1139
|
+
expect(getCallSession(session.id)!.status).toBe("completed");
|
|
1140
|
+
|
|
1141
|
+
controller.destroy();
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
test("destroy cancels a pending end-call teardown mid-drain", async () => {
|
|
1145
|
+
mockEndCallListenWindowMs = 0;
|
|
1146
|
+
let resolveDrain!: () => void;
|
|
1147
|
+
const drainPromise = new Promise<void>((r) => {
|
|
1148
|
+
resolveDrain = r;
|
|
1149
|
+
});
|
|
1150
|
+
mockStartVoiceTurn.mockImplementation(
|
|
1151
|
+
createMockVoiceTurn(["Goodbye! ", "[END_CALL]"]),
|
|
1152
|
+
);
|
|
1153
|
+
const { session, relay, controller } = setupController(undefined, {
|
|
1154
|
+
awaitPlaybackDrained: () => drainPromise,
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
await controller.handleCallerUtterance("That is all, thanks");
|
|
1158
|
+
expect(relay.endCalled).toBe(false);
|
|
1159
|
+
|
|
1160
|
+
controller.destroy();
|
|
1161
|
+
|
|
1162
|
+
// Drain resolves after destroy — teardown must not fire endSession.
|
|
1163
|
+
resolveDrain();
|
|
1164
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
1165
|
+
expect(relay.endCalled).toBe(false);
|
|
1166
|
+
expect(getCallSession(session.id)!.status).toBe("in_progress");
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
test("destroy during the listen window settles the wait without hanging up", async () => {
|
|
1170
|
+
// Long window so destroy lands while the listen-window wait is armed;
|
|
1171
|
+
// the wait must settle (not leak) and teardown must not fire endSession.
|
|
1172
|
+
mockEndCallListenWindowMs = 10_000;
|
|
1173
|
+
const drainPromise = Promise.resolve();
|
|
1174
|
+
mockStartVoiceTurn.mockImplementation(
|
|
1175
|
+
createMockVoiceTurn(["Goodbye! ", "[END_CALL]"]),
|
|
1176
|
+
);
|
|
1177
|
+
const { relay, controller } = setupController(undefined, {
|
|
1178
|
+
awaitPlaybackDrained: () => drainPromise,
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
await controller.handleCallerUtterance("That is all, thanks");
|
|
1182
|
+
// Let the drain phase resolve and the listen-window timer arm.
|
|
1183
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
1184
|
+
expect(relay.endCalled).toBe(false);
|
|
1185
|
+
|
|
1186
|
+
controller.destroy();
|
|
1187
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
1188
|
+
expect(relay.endCalled).toBe(false);
|
|
1189
|
+
});
|
|
1190
|
+
|
|
900
1191
|
// ── handleUserAnswer ──────────────────────────────────────────────
|
|
901
1192
|
|
|
902
1193
|
test("handleUserAnswer: returns true immediately and fires LLM asynchronously", async () => {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies `CallSiteRoutingProvider` auto-resolves a connection for
|
|
3
|
+
* connection-less profiles even when the resolved provider matches the
|
|
4
|
+
* default provider's name.
|
|
5
|
+
*
|
|
6
|
+
* On platform-hosted installs the default transport rides the managed
|
|
7
|
+
* (platform-billed) `vellum` connection while presenting the upstream
|
|
8
|
+
* provider's name (e.g. "anthropic"). A BYOK profile `{provider:
|
|
9
|
+
* "anthropic"}` with no explicit `provider_connection` must route through
|
|
10
|
+
* the user's own connection when one exists — reusing the default on a bare
|
|
11
|
+
* name match silently bills managed credits for a BYOK-intent profile.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
15
|
+
|
|
16
|
+
import * as realDb from "../persistence/db-connection.js";
|
|
17
|
+
import * as realConnections from "../providers/inference/connections.js";
|
|
18
|
+
import type {
|
|
19
|
+
Message,
|
|
20
|
+
Provider,
|
|
21
|
+
ProviderResponse,
|
|
22
|
+
SendMessageOptions,
|
|
23
|
+
} from "../providers/types.js";
|
|
24
|
+
import { setConfig } from "./helpers/set-config.js";
|
|
25
|
+
|
|
26
|
+
let connectionRows: Array<Record<string, unknown>> = [];
|
|
27
|
+
|
|
28
|
+
mock.module("../providers/inference/connections.js", () => ({
|
|
29
|
+
...realConnections,
|
|
30
|
+
listConnections: () => connectionRows,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
mock.module("../persistence/db-connection.js", () => ({
|
|
34
|
+
...realDb,
|
|
35
|
+
getDb: () => ({}),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const { CallSiteRoutingProvider } =
|
|
39
|
+
await import("../providers/call-site-routing.js");
|
|
40
|
+
|
|
41
|
+
const DUMMY_MESSAGES: Message[] = [
|
|
42
|
+
{ role: "user", content: [{ type: "text", text: "hi" }] },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
function makeProvider(name: string, onCall: () => void): Provider {
|
|
46
|
+
return {
|
|
47
|
+
name,
|
|
48
|
+
async sendMessage(
|
|
49
|
+
_messages: Message[],
|
|
50
|
+
_options?: SendMessageOptions,
|
|
51
|
+
): Promise<ProviderResponse> {
|
|
52
|
+
onCall();
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: "ok" }],
|
|
55
|
+
model: name,
|
|
56
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
57
|
+
stopReason: "end_turn",
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const PERSONAL_CONNECTION = {
|
|
64
|
+
name: "anthropic-personal",
|
|
65
|
+
provider: "anthropic",
|
|
66
|
+
auth: { type: "api_key", credential: "credential/anthropic/api_key" },
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
connectionRows = [];
|
|
71
|
+
// Same provider name as the default transport; no provider_connection on
|
|
72
|
+
// the profile — the managed-install shape that must not silently reuse
|
|
73
|
+
// the default transport.
|
|
74
|
+
setConfig("llm", {
|
|
75
|
+
default: {
|
|
76
|
+
provider: "anthropic",
|
|
77
|
+
model: "claude-opus-4-8",
|
|
78
|
+
provider_connection: "vellum",
|
|
79
|
+
},
|
|
80
|
+
profiles: {
|
|
81
|
+
byok: { provider: "anthropic", model: "claude-fable-5" },
|
|
82
|
+
},
|
|
83
|
+
callSites: {
|
|
84
|
+
memoryRetrieval: { profile: "byok" },
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("CallSiteRoutingProvider connection auto-resolve", () => {
|
|
90
|
+
test("routes a connection-less same-provider profile through the user's own connection", async () => {
|
|
91
|
+
connectionRows = [PERSONAL_CONNECTION];
|
|
92
|
+
|
|
93
|
+
const calls = { default: 0, personal: 0 };
|
|
94
|
+
const defaultProvider = makeProvider("anthropic", () => {
|
|
95
|
+
calls.default++;
|
|
96
|
+
});
|
|
97
|
+
const personalProvider = makeProvider("anthropic", () => {
|
|
98
|
+
calls.personal++;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const resolvedNames: string[] = [];
|
|
102
|
+
const wrapped = new CallSiteRoutingProvider(
|
|
103
|
+
defaultProvider,
|
|
104
|
+
async (connectionName: string) => {
|
|
105
|
+
resolvedNames.push(connectionName);
|
|
106
|
+
return connectionName === "anthropic-personal"
|
|
107
|
+
? personalProvider
|
|
108
|
+
: null;
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
await wrapped.sendMessage(DUMMY_MESSAGES, {
|
|
113
|
+
config: { callSite: "memoryRetrieval" },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(resolvedNames).toEqual(["anthropic-personal"]);
|
|
117
|
+
expect(calls.personal).toBe(1);
|
|
118
|
+
expect(calls.default).toBe(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("reuses the default transport when no connection exists for the provider", async () => {
|
|
122
|
+
connectionRows = [];
|
|
123
|
+
|
|
124
|
+
const calls = { default: 0 };
|
|
125
|
+
const defaultProvider = makeProvider("anthropic", () => {
|
|
126
|
+
calls.default++;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const resolvedNames: string[] = [];
|
|
130
|
+
const wrapped = new CallSiteRoutingProvider(
|
|
131
|
+
defaultProvider,
|
|
132
|
+
async (connectionName: string) => {
|
|
133
|
+
resolvedNames.push(connectionName);
|
|
134
|
+
return null;
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
await wrapped.sendMessage(DUMMY_MESSAGES, {
|
|
139
|
+
config: { callSite: "memoryRetrieval" },
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(resolvedNames).toEqual([]);
|
|
143
|
+
expect(calls.default).toBe(1);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("falls back to the default transport when the auto-resolved connection soft-fails", async () => {
|
|
147
|
+
connectionRows = [PERSONAL_CONNECTION];
|
|
148
|
+
|
|
149
|
+
const calls = { default: 0 };
|
|
150
|
+
const defaultProvider = makeProvider("anthropic", () => {
|
|
151
|
+
calls.default++;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const wrapped = new CallSiteRoutingProvider(
|
|
155
|
+
defaultProvider,
|
|
156
|
+
async () => null,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
await wrapped.sendMessage(DUMMY_MESSAGES, {
|
|
160
|
+
config: { callSite: "memoryRetrieval" },
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(calls.default).toBe(1);
|
|
164
|
+
});
|
|
165
|
+
});
|