@vellumai/assistant 0.12.1-staging.1 → 0.12.1
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/AGENTS.md +0 -4
- package/ARCHITECTURE.md +12 -11
- package/docs/architecture/turn-actor.md +8 -15
- package/openapi.yaml +1 -291
- package/package.json +1 -1
- package/src/__tests__/always-loaded-tools-guard.test.ts +10 -18
- package/src/__tests__/app-compiler.test.ts +0 -6
- package/src/__tests__/app-control-tool-schemas.test.ts +2 -5
- package/src/__tests__/app-dir-path-guard.test.ts +3 -1
- package/src/__tests__/app-source-watcher.test.ts +218 -0
- package/src/{calls/__tests__ → __tests__}/call-controller.test.ts +82 -503
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +2 -5
- package/src/__tests__/computer-use-tools.test.ts +5 -17
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +29 -28
- package/src/__tests__/config-watcher-skill-reseed.test.ts +359 -0
- package/src/__tests__/config-watcher.test.ts +8 -23
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +0 -26
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -4
- package/src/__tests__/conversation-queue.test.ts +207 -48
- package/src/__tests__/conversation-retry-discard.test.ts +1 -54
- package/src/__tests__/conversation-skill-tools.test.ts +0 -27
- package/src/__tests__/conversation-surfaces-app-control.test.ts +3 -8
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -67
- package/src/__tests__/conversation-title-service.test.ts +10 -14
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +5 -3
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -51
- package/src/__tests__/credential-record-retire.test.ts +1 -171
- package/src/__tests__/credential-routes.test.ts +3 -212
- package/src/__tests__/credential-security-invariants.test.ts +1 -2
- package/src/__tests__/cu-unified-flow.test.ts +2 -8
- package/src/__tests__/drain-kick-guard.test.ts +2 -0
- package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/host-bash-proxy.test.ts +45 -26
- package/src/__tests__/host-browser-proxy.test.ts +17 -6
- package/src/__tests__/host-file-proxy-targeted.test.ts +0 -23
- package/src/__tests__/host-file-proxy.test.ts +23 -11
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +9 -30
- package/src/__tests__/llm-catalog-parity.test.ts +0 -25
- package/src/__tests__/mcp-auth-routes.test.ts +9 -6
- package/src/__tests__/mcp-config-secret-boundary.test.ts +136 -78
- package/src/__tests__/mcp-health-check.test.ts +11 -8
- package/src/__tests__/mcp-list-plugin-servers.test.ts +10 -8
- package/src/{calls/__tests__ → __tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-output.test.ts +8 -37
- package/src/{calls/__tests__ → __tests__}/media-stream-parser.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-server-integration.test.ts +57 -150
- package/src/{calls/__tests__ → __tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{calls/__tests__ → __tests__}/media-turn-detector.test.ts +1 -1
- package/src/__tests__/messages-after-tiebreaker.test.ts +0 -91
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +548 -355
- package/src/__tests__/model-intents.test.ts +0 -3
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-client-verify-credential.test.ts +0 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -13
- package/src/__tests__/script-proxy-certs.test.ts +0 -35
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/speaker-identification.test.ts +56 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +3 -19
- package/src/__tests__/subagent-tools.test.ts +2 -3
- package/src/__tests__/telephony-tts-guard.test.ts +3 -38
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -4
- package/src/{calls/__tests__/voice-session-bridge-turn-flow.test.ts → __tests__/voice-session-bridge.test.ts} +12 -20
- package/src/__tests__/watch-retro-tool-availability.test.ts +17 -8
- package/src/__tests__/workspace-git-service.test.ts +0 -26
- package/src/acp/__tests__/agent-process.test.ts +4 -258
- package/src/acp/__tests__/auth-required.test.ts +0 -96
- package/src/acp/__tests__/client-handler.test.ts +2 -90
- package/src/acp/__tests__/session-manager-persistence.test.ts +1 -5
- package/src/acp/__tests__/session-manager-resume.test.ts +6 -270
- package/src/acp/__tests__/session-manager-usage.test.ts +2 -4
- package/src/acp/__tests__/session-manager.test.ts +4 -895
- package/src/acp/agent-process.test.ts +1 -4
- package/src/acp/agent-process.ts +9 -94
- package/src/acp/auth-required.ts +1 -50
- package/src/acp/auto-install.test.ts +7 -9
- package/src/acp/client-handler.ts +3 -16
- package/src/acp/resolve-agent.test.ts +6 -220
- package/src/acp/resolve-agent.ts +23 -88
- package/src/acp/session-manager.test.ts +3 -196
- package/src/acp/session-manager.ts +18 -441
- package/src/acp/types.ts +5 -50
- package/src/api/events/app-files-changed.ts +3 -4
- package/src/api/index.ts +0 -6
- package/src/apps/app-store.ts +1 -1
- package/src/bundler/app-compiler.ts +0 -15
- package/src/calls/__tests__/voice-control-protocol.test.ts +25 -58
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -37
- package/src/calls/__tests__/voice-triage-escalate.test.ts +0 -105
- package/src/calls/call-constants.ts +4 -24
- package/src/calls/call-controller.ts +199 -499
- package/src/calls/call-transport.ts +0 -16
- package/src/calls/media-stream-output.ts +0 -32
- package/src/calls/media-stream-server.ts +30 -82
- package/src/calls/media-stream-stt-session.ts +0 -15
- package/src/calls/resolve-call-tts-provider.ts +2 -33
- package/src/calls/speaker-identification.ts +233 -0
- package/src/calls/tts-call-strategy.ts +5 -9
- package/src/calls/types.ts +0 -2
- package/src/calls/voice-control-protocol.ts +21 -53
- package/src/calls/voice-session-bridge.ts +46 -59
- package/src/calls/voice-triage-escalate.ts +56 -175
- package/src/cli/commands/apps.help.ts +13 -67
- package/src/cli/commands/apps.ts +0 -183
- package/src/cli/commands/channels/index.help.ts +2 -70
- package/src/cli/commands/channels/index.ts +3 -16
- package/src/cli/commands/credentials.help.ts +2 -6
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/roadmap.help.ts +1 -5
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +90 -103
- package/src/config/__tests__/default-profile-catalog.test.ts +0 -22
- package/src/config/__tests__/memory-retrospective-schema.test.ts +1 -15
- package/src/config/__tests__/platform-rehydration.test.ts +0 -93
- package/src/config/acp-defaults.test.ts +3 -13
- package/src/config/acp-defaults.ts +6 -23
- package/src/config/acp-schema.ts +2 -30
- package/src/config/bundled-skills/acp/SKILL.md +14 -22
- package/src/config/bundled-skills/acp/TOOLS.json +1 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +9 -12
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +139 -67
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +1 -8
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +1 -4
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +12 -8
- package/src/config/memory-v3-gate.test.ts +0 -31
- package/src/config/memory-v3-gate.ts +16 -20
- package/src/config/platform-rehydration.ts +21 -75
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +158 -8
- package/src/config/schemas/calls.ts +28 -1
- package/src/config/schemas/live-voice.ts +173 -1
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +0 -10
- package/src/config/schemas/skills.ts +14 -0
- package/src/credential-execution/process-manager.test.ts +19 -18
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +121 -66
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -4
- package/src/daemon/app-source-watcher.ts +218 -0
- package/src/daemon/config-watcher.ts +194 -13
- package/src/daemon/conversation-process.ts +64 -35
- package/src/daemon/conversation-skill-tools.ts +31 -13
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +8 -10
- package/src/daemon/conversation-tool-setup.ts +43 -37
- package/src/daemon/conversation.ts +3 -0
- package/src/daemon/host-bash-proxy.ts +12 -8
- package/src/daemon/host-browser-proxy.ts +1 -15
- package/src/daemon/host-file-proxy.ts +10 -8
- package/src/daemon/host-transfer-proxy.ts +18 -14
- package/src/daemon/lifecycle.ts +5 -0
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/shutdown-handlers.ts +9 -1
- package/src/daemon/skill-memory-refresh.ts +0 -64
- package/src/daemon/tool-side-effects.ts +18 -7
- package/src/hooks/types.ts +0 -33
- package/src/inbound/platform-callback-registration.ts +5 -4
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +3 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/{calls → live-voice}/__tests__/progress-narration.test.ts +4 -4
- package/src/{calls → live-voice}/__tests__/progress-phrases.test.ts +1 -1
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +69 -13
- package/src/live-voice/live-voice-session.ts +693 -213
- package/src/{calls → live-voice}/progress-narration.ts +2 -2
- package/src/live-voice/protocol.ts +2 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +45 -29
- package/src/mcp/startup.ts +4 -8
- package/src/messaging/provider.ts +3 -10
- package/src/messaging/providers/callback-routing.ts +0 -18
- package/src/messaging/providers/channel-transport.ts +0 -56
- package/src/messaging/providers/discord/transport.ts +0 -17
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +17 -6
- package/src/messaging/providers/slack/adapter.ts +44 -7
- package/src/messaging/providers/slack/transport.ts +0 -16
- package/src/messaging/providers/telegram-bot/adapter.ts +48 -1
- package/src/messaging/providers/telegram-bot/client.ts +26 -0
- package/src/messaging/providers/telegram-bot/transport.ts +0 -20
- package/src/messaging/providers/whatsapp/adapter.ts +48 -1
- package/src/messaging/providers/whatsapp/client.ts +29 -0
- package/src/messaging/providers/whatsapp/transport.ts +0 -18
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +0 -8
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +0 -55
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +3 -153
- package/src/notifications/assistant-reply-producer.ts +3 -8
- package/src/notifications/conversation-pairing.ts +16 -36
- package/src/notifications/delivered-post-record.ts +1 -11
- package/src/notifications/home-feed-side-effect.ts +7 -55
- package/src/outbound-proxy/certs.ts +5 -3
- package/src/permissions/checker.test.ts +6 -11
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +63 -71
- package/src/persistence/conversation-lineage.ts +4 -12
- package/src/persistence/conversation-title-service.ts +26 -9
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-reads.ts +0 -30
- package/src/persistence/migrations/157-invite-contact-id.ts +6 -18
- package/src/persistence/schema/memory-core.ts +0 -5
- package/src/platform/client.test.ts +0 -2
- package/src/platform/client.ts +10 -2
- package/src/plugin-api/constants.ts +0 -2
- package/src/plugin-api/index.ts +0 -1
- package/src/plugin-api/types.ts +0 -2
- package/src/plugins/defaults/index.ts +0 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +0 -46
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +5 -9
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +10 -20
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +0 -219
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +0 -31
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -63
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +0 -4
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -4
- package/src/plugins/defaults/memory/hooks/init.ts +0 -10
- package/src/plugins/defaults/memory/hooks/shutdown.ts +0 -2
- package/src/plugins/defaults/memory/jobs-worker.ts +0 -13
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +60 -34
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +5 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -12
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +71 -312
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +3 -5
- package/src/plugins/defaults/memory/startup.ts +1 -6
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +9 -7
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +0 -89
- package/src/plugins/defaults/memory/worker.ts +0 -6
- package/src/plugins/mcp-servers.ts +75 -15
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/gemini/client.ts +23 -9
- package/src/providers/model-catalog.ts +0 -34
- package/src/providers/model-intents.ts +5 -10
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +18 -146
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +1 -83
- package/src/providers/openai/chat-completions-provider.ts +78 -192
- package/src/providers/openai/codex-models.ts +4 -0
- package/src/runtime/AGENTS.md +0 -2
- package/src/runtime/auth/same-actor.ts +3 -38
- package/src/runtime/http-server.ts +151 -160
- package/src/runtime/routes/__tests__/acp-routes.test.ts +4 -159
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -108
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +12 -16
- package/src/runtime/routes/acp-routes.test.ts +0 -15
- package/src/runtime/routes/acp-routes.ts +9 -72
- package/src/runtime/routes/app-management-routes.ts +0 -73
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +3 -14
- package/src/runtime/routes/conversation-query-routes.ts +0 -40
- package/src/runtime/routes/credential-routes.ts +10 -19
- package/src/runtime/routes/diagnostics-routes.ts +3 -55
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/log-export-routes.ts +0 -2
- package/src/runtime/routes/mcp-auth-routes.ts +47 -39
- package/src/runtime/routes/migration-routes.ts +21 -8
- package/src/runtime/routes/platform-routes.ts +15 -18
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -28
- package/src/runtime/services/conversation-serializer.ts +3 -16
- package/src/runtime/sync/resource-sync-events.ts +1 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/ces-rpc-record-backend.ts +0 -7
- package/src/subagent/consult-context.ts +25 -12
- package/src/tools/acp/list-agents.test.ts +1 -3
- package/src/tools/acp/spawn.test.ts +11 -146
- package/src/tools/acp/spawn.ts +3 -21
- package/src/tools/acp/status.ts +3 -40
- package/src/tools/app-control/skill-proxy-bridge.ts +1 -2
- package/src/tools/capability-offer.test.ts +2 -22
- package/src/tools/capability-offer.ts +1 -16
- package/src/tools/computer-use/definitions.ts +2 -4
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +0 -100
- package/src/tools/host-filesystem/edit.test.ts +1 -2
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +1 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +1 -2
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +1 -2
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +0 -16
- package/src/tools/host-terminal/host-shell.ts +20 -4
- package/src/tools/policy-context.ts +7 -6
- package/src/tools/subagent/spawn.ts +8 -5
- package/src/tools/watch/watch-retro-report.ts +8 -0
- package/src/util/__tests__/language-subtag.test.ts +1 -23
- package/src/util/fs-watcher-error.ts +36 -0
- package/src/util/language-subtag.ts +0 -16
- package/src/util/platform.ts +0 -8
- package/src/workspace/git-service.ts +54 -5
- package/src/workspace/migrations/registry.ts +0 -6
- package/scripts/smoke-desktop-window-theme.ts +0 -186
- package/src/__tests__/git-porcelain.test.ts +0 -47
- package/src/__tests__/helpers/set-workspace-mcp.ts +0 -11
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +0 -119
- package/src/__tests__/resolve-app.test.ts +0 -135
- package/src/__tests__/source-fingerprint.test.ts +0 -184
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +0 -336
- package/src/acp/__tests__/helpers/acp-model-option.ts +0 -51
- package/src/acp/model-config.test.ts +0 -166
- package/src/acp/model-config.ts +0 -138
- package/src/api/events/acp-session-model-update.test.ts +0 -88
- package/src/api/events/acp-session-model-update.ts +0 -46
- package/src/apps/resolve-app.ts +0 -81
- package/src/apps/source-fingerprint.ts +0 -266
- package/src/calls/__tests__/barge-in-guard.test.ts +0 -81
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +0 -229
- package/src/calls/__tests__/voice-progress-cadence.test.ts +0 -274
- package/src/calls/barge-in-guard.ts +0 -118
- package/src/calls/voice-leg-coordinator.ts +0 -189
- package/src/calls/voice-progress-cadence.ts +0 -423
- package/src/cli/commands/__tests__/apps.test.ts +0 -245
- package/src/cli/commands/__tests__/roadmap.help.test.ts +0 -19
- package/src/cli/commands/channels/__tests__/send.test.ts +0 -218
- package/src/cli/commands/channels/send.ts +0 -128
- package/src/config/__tests__/platform-identity.test.ts +0 -117
- package/src/config/acp-agent-write.ts +0 -43
- package/src/config/acp-schema.test.ts +0 -81
- package/src/config/platform-identity.ts +0 -144
- package/src/config/schemas/__tests__/voice.test.ts +0 -148
- package/src/config/schemas/voice.ts +0 -150
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +0 -133
- package/src/daemon/app-change-notify.ts +0 -45
- package/src/desktop/__tests__/fake-desktop.ts +0 -119
- package/src/desktop/desktop-chrome-policy.test.ts +0 -71
- package/src/desktop/desktop-chrome-policy.ts +0 -42
- package/src/desktop/desktop-dependencies.test.ts +0 -102
- package/src/desktop/desktop-dependencies.ts +0 -306
- package/src/desktop/desktop-feature.test.ts +0 -18
- package/src/desktop/desktop-feature.ts +0 -19
- package/src/desktop/desktop-panel-config.test.ts +0 -135
- package/src/desktop/desktop-panel-config.ts +0 -149
- package/src/desktop/desktop-session-manager.test.ts +0 -627
- package/src/desktop/desktop-session-manager.ts +0 -814
- package/src/desktop/desktop-stream-bridge.test.ts +0 -395
- package/src/desktop/desktop-stream-bridge.ts +0 -214
- package/src/desktop/desktop-wallpaper.test.ts +0 -191
- package/src/desktop/desktop-wallpaper.ts +0 -113
- package/src/desktop/desktop-window-theme.ts +0 -170
- package/src/i18n/AGENTS.md +0 -22
- package/src/i18n/__tests__/i18n.test.ts +0 -108
- package/src/i18n/index.ts +0 -16
- package/src/i18n/locales.ts +0 -93
- package/src/i18n/messages.ts +0 -61
- package/src/i18n/title.ts +0 -25
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +0 -135
- package/src/mcp/spec-schema.ts +0 -111
- package/src/mcp/workspace-mcp-config.ts +0 -188
- package/src/messaging/providers/__tests__/proactive-address.test.ts +0 -95
- package/src/persistence/conversation-title-placeholders.ts +0 -23
- package/src/persistence/message-cursor.ts +0 -78
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +0 -149
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +0 -118
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +0 -83
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +0 -21
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +0 -126
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +0 -79
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +0 -28
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +0 -9
- package/src/providers/gemini-thought-signature.test.ts +0 -63
- package/src/providers/gemini-thought-signature.ts +0 -44
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +0 -613
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +0 -235
- package/src/providers/openai/google-thought-signature.ts +0 -320
- package/src/runtime/__tests__/channel-send.test.ts +0 -443
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +0 -89
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +0 -58
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +0 -72
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +0 -59
- package/src/runtime/channel-send.ts +0 -316
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +0 -205
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +0 -127
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
- package/src/runtime/routes/channel-send-routes.ts +0 -85
- package/src/runtime/routes/desktop-setup-routes.test.ts +0 -74
- package/src/runtime/routes/desktop-setup-routes.ts +0 -35
- package/src/security/__tests__/ces-http-record-backend.test.ts +0 -137
- package/src/security/ces-http-record-backend.ts +0 -120
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +0 -60
- package/src/skills/workspace-skill-md-signature.ts +0 -50
- package/src/tools/acp/status.test.ts +0 -94
- package/src/util/ca-bundle.ts +0 -13
- package/src/workspace/git-porcelain.ts +0 -66
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +0 -258
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +0 -78
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +0 -214
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +0 -102
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +0 -199
- /package/src/{calls → live-voice}/progress-phrases.ts +0 -0
|
@@ -8,17 +8,17 @@ import {
|
|
|
8
8
|
test,
|
|
9
9
|
} from "bun:test";
|
|
10
10
|
|
|
11
|
-
mock.module("
|
|
11
|
+
mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
|
|
12
12
|
|
|
13
13
|
// Guardian requests/deliveries are created through the gateway client; serve
|
|
14
14
|
// that surface from the in-memory sim so dispatch never opens IPC.
|
|
15
15
|
import {
|
|
16
16
|
bridgeState,
|
|
17
17
|
gatewayGuardianRequestsStoreBridge,
|
|
18
|
-
} from "
|
|
18
|
+
} from "./helpers/gateway-guardian-requests-store-bridge.js";
|
|
19
19
|
|
|
20
20
|
mock.module(
|
|
21
|
-
"
|
|
21
|
+
"../channels/gateway-guardian-requests.js",
|
|
22
22
|
() => gatewayGuardianRequestsStoreBridge,
|
|
23
23
|
);
|
|
24
24
|
|
|
@@ -36,7 +36,7 @@ mock.module(
|
|
|
36
36
|
let mockResolvableProviderKeys: ((service: string) => string | null) | null =
|
|
37
37
|
null;
|
|
38
38
|
|
|
39
|
-
mock.module("
|
|
39
|
+
mock.module("../security/secure-keys.js", () => ({
|
|
40
40
|
getSecureKeyAsync: async () => null,
|
|
41
41
|
getSecureKey: () => null,
|
|
42
42
|
getProviderKeyAsync: async (service: string) =>
|
|
@@ -45,19 +45,7 @@ mock.module("../../security/secure-keys.js", () => ({
|
|
|
45
45
|
: "test-key",
|
|
46
46
|
}));
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
// store. The controller tests drive the bridge mock on a microtask budget, so
|
|
50
|
-
// the configured providers stand as the effective ones here.
|
|
51
|
-
mock.module("../../config/managed-speech-defaults.js", () => ({
|
|
52
|
-
resolveEffectiveSpeechProviders: async (config?: {
|
|
53
|
-
services: { stt: { provider: string }; tts: { provider: string } };
|
|
54
|
-
}) => {
|
|
55
|
-
const services = (config ?? loadConfig()).services;
|
|
56
|
-
return { stt: services.stt.provider, tts: services.tts.provider };
|
|
57
|
-
},
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
|
-
mock.module("../../security/credential-key.js", () => ({
|
|
48
|
+
mock.module("../security/credential-key.js", () => ({
|
|
61
49
|
credentialKey: (...args: string[]) => args.join("/"),
|
|
62
50
|
}));
|
|
63
51
|
|
|
@@ -68,10 +56,7 @@ let mockSilenceTimeoutMs = 30_000;
|
|
|
68
56
|
let mockEndCallListenWindowMs = 0;
|
|
69
57
|
let mockEndCallDrainMaxWaitMs = 15_000;
|
|
70
58
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
mock.module("../call-constants.js", () => ({
|
|
74
|
-
...realCallConstants,
|
|
59
|
+
mock.module("../calls/call-constants.js", () => ({
|
|
75
60
|
getMaxCallDurationMs: () => 12 * 60 * 1000,
|
|
76
61
|
getUserConsultationTimeoutMs: () => mockConsultationTimeoutMs,
|
|
77
62
|
getSilenceTimeoutMs: () => mockSilenceTimeoutMs,
|
|
@@ -125,7 +110,7 @@ let mockStartVoiceTurn: Mock<any>;
|
|
|
125
110
|
|
|
126
111
|
// ── Notification pipeline mock (prevent async handle leaks from fire-and-forget dispatches) ──
|
|
127
112
|
|
|
128
|
-
mock.module("
|
|
113
|
+
mock.module("../notifications/emit-signal.js", () => ({
|
|
129
114
|
emitNotificationSignal: async () => ({
|
|
130
115
|
signalId: "mock-signal",
|
|
131
116
|
deduplicated: false,
|
|
@@ -137,7 +122,7 @@ mock.module("../../notifications/emit-signal.js", () => ({
|
|
|
137
122
|
|
|
138
123
|
// Guardian principalId is resolved from the gateway binding reader. Mirror the
|
|
139
124
|
// vellum binding seeded by resetTables so guardian dispatch can resolve it.
|
|
140
|
-
mock.module("
|
|
125
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
141
126
|
getGuardianDelivery: async () => [
|
|
142
127
|
{
|
|
143
128
|
channelType: "vellum",
|
|
@@ -154,7 +139,7 @@ mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
|
154
139
|
anyGuardian: (list: unknown[]) => list[0],
|
|
155
140
|
}));
|
|
156
141
|
|
|
157
|
-
mock.module("../voice-session-bridge.js", () => {
|
|
142
|
+
mock.module("../calls/voice-session-bridge.js", () => {
|
|
158
143
|
mockStartVoiceTurn = mock(createMockVoiceTurn(["Hello", " there"]));
|
|
159
144
|
return {
|
|
160
145
|
startVoiceTurn: (...args: unknown[]) => mockStartVoiceTurn(...args),
|
|
@@ -170,8 +155,8 @@ mock.module("../voice-session-bridge.js", () => {
|
|
|
170
155
|
import {
|
|
171
156
|
_resetTtsProviderOverridesForTests,
|
|
172
157
|
_setTtsProviderForTests,
|
|
173
|
-
} from "
|
|
174
|
-
import type { TtsProvider } from "
|
|
158
|
+
} from "../tts/provider-catalog.js";
|
|
159
|
+
import type { TtsProvider } from "../tts/types.js";
|
|
175
160
|
|
|
176
161
|
function registerTestTtsProviders(): void {
|
|
177
162
|
_resetTtsProviderOverridesForTests();
|
|
@@ -221,33 +206,26 @@ registerTestTtsProviders();
|
|
|
221
206
|
|
|
222
207
|
// ── Import source modules after all mocks are registered ────────────
|
|
223
208
|
|
|
224
|
-
import {
|
|
225
|
-
import {
|
|
226
|
-
import { setConfig } from "../../__tests__/helpers/set-config.js";
|
|
227
|
-
import { loadConfig } from "../../config/loader.js";
|
|
228
|
-
import type { VoiceProgressConfig } from "../../config/schemas/voice.js";
|
|
229
|
-
import { getMessages } from "../../persistence/conversation-crud.js";
|
|
230
|
-
import { getDb } from "../../persistence/db-connection.js";
|
|
231
|
-
import { initializeDb } from "../../persistence/db-init.js";
|
|
232
|
-
import { resetTestTables } from "../../persistence/raw-query.js";
|
|
233
|
-
import { conversations } from "../../persistence/schema/index.js";
|
|
234
|
-
import { CallController } from "../call-controller.js";
|
|
235
|
-
import { getCallController } from "../call-state.js";
|
|
209
|
+
import { CallController } from "../calls/call-controller.js";
|
|
210
|
+
import { getCallController } from "../calls/call-state.js";
|
|
236
211
|
import {
|
|
237
212
|
createCallSession,
|
|
238
213
|
getCallEvents,
|
|
239
214
|
getCallSession,
|
|
240
215
|
getPendingQuestion,
|
|
241
216
|
updateCallSession,
|
|
242
|
-
} from "../call-store.js";
|
|
243
|
-
import type { CallTransport } from "../call-transport.js";
|
|
244
|
-
import
|
|
245
|
-
import {
|
|
246
|
-
import {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
} from "../
|
|
217
|
+
} from "../calls/call-store.js";
|
|
218
|
+
import type { CallTransport } from "../calls/call-transport.js";
|
|
219
|
+
import { resolveCallTtsProvider } from "../calls/resolve-call-tts-provider.js";
|
|
220
|
+
import { loadConfig } from "../config/loader.js";
|
|
221
|
+
import { getMessages } from "../persistence/conversation-crud.js";
|
|
222
|
+
import { getDb } from "../persistence/db-connection.js";
|
|
223
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
224
|
+
import { resetTestTables } from "../persistence/raw-query.js";
|
|
225
|
+
import { conversations } from "../persistence/schema/index.js";
|
|
226
|
+
import { resetDbForTesting } from "./db-test-helpers.js";
|
|
227
|
+
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
228
|
+
import { setConfig } from "./helpers/set-config.js";
|
|
251
229
|
|
|
252
230
|
// Disable memory so persisted call messages skip background indexing, and
|
|
253
231
|
// seed the ingress base URL used for synthesized-audio play URLs.
|
|
@@ -267,8 +245,6 @@ afterAll(() => {
|
|
|
267
245
|
|
|
268
246
|
interface MockTransport extends CallTransport {
|
|
269
247
|
sentTokens: Array<{ token: string; last: boolean }>;
|
|
270
|
-
/** Tokens sent with the system-copy flag (no caller-language hint). */
|
|
271
|
-
systemCopyTokens: string[];
|
|
272
248
|
sentPlayUrls: string[];
|
|
273
249
|
endCalled: boolean;
|
|
274
250
|
endReason: string | undefined;
|
|
@@ -278,7 +254,6 @@ interface MockTransport extends CallTransport {
|
|
|
278
254
|
function createMockTransport(): MockTransport {
|
|
279
255
|
const state = {
|
|
280
256
|
sentTokens: [] as Array<{ token: string; last: boolean }>,
|
|
281
|
-
systemCopyTokens: [] as string[],
|
|
282
257
|
sentPlayUrls: [] as string[],
|
|
283
258
|
_endCalled: false,
|
|
284
259
|
_endReason: undefined as string | undefined,
|
|
@@ -289,9 +264,6 @@ function createMockTransport(): MockTransport {
|
|
|
289
264
|
get sentTokens() {
|
|
290
265
|
return state.sentTokens;
|
|
291
266
|
},
|
|
292
|
-
get systemCopyTokens() {
|
|
293
|
-
return state.systemCopyTokens;
|
|
294
|
-
},
|
|
295
267
|
get sentPlayUrls() {
|
|
296
268
|
return state.sentPlayUrls;
|
|
297
269
|
},
|
|
@@ -304,15 +276,8 @@ function createMockTransport(): MockTransport {
|
|
|
304
276
|
get cancelPendingSpeechCount() {
|
|
305
277
|
return state._cancelPendingSpeechCount;
|
|
306
278
|
},
|
|
307
|
-
sendTextToken(
|
|
308
|
-
token: string,
|
|
309
|
-
last: boolean,
|
|
310
|
-
opts?: { systemCopy?: boolean },
|
|
311
|
-
) {
|
|
279
|
+
sendTextToken(token: string, last: boolean) {
|
|
312
280
|
state.sentTokens.push({ token, last });
|
|
313
|
-
if (opts?.systemCopy === true) {
|
|
314
|
-
state.systemCopyTokens.push(token);
|
|
315
|
-
}
|
|
316
281
|
},
|
|
317
282
|
sendPlayUrl(url: string) {
|
|
318
283
|
state.sentPlayUrls.push(url);
|
|
@@ -407,16 +372,13 @@ function setupController(
|
|
|
407
372
|
task?: string,
|
|
408
373
|
opts?: {
|
|
409
374
|
assistantId?: string;
|
|
410
|
-
trustContext?: import("
|
|
375
|
+
trustContext?: import("../daemon/trust-context-types.js").TrustContext;
|
|
411
376
|
/** Simulate the media-stream transport's PCM requirement. */
|
|
412
377
|
requiresPcmAudio?: boolean;
|
|
413
378
|
/** Simulate a transport that gates teardown on playback drain. */
|
|
414
379
|
awaitPlaybackDrained?: () => Promise<void>;
|
|
415
380
|
/** Synthesis-language resolver, as the media-stream server wires it. */
|
|
416
381
|
resolveSynthesisLanguage?: () => string | undefined;
|
|
417
|
-
/** Progress narration seam; an explicit null keeps the call silent. */
|
|
418
|
-
progressNarrator?: VoiceProgressNarrator | null;
|
|
419
|
-
progressConfig?: VoiceProgressConfig;
|
|
420
382
|
},
|
|
421
383
|
) {
|
|
422
384
|
ensureConversation("conv-ctrl-test");
|
|
@@ -441,11 +403,6 @@ function setupController(
|
|
|
441
403
|
assistantId: opts?.assistantId,
|
|
442
404
|
trustContext: opts?.trustContext,
|
|
443
405
|
resolveSynthesisLanguage: opts?.resolveSynthesisLanguage,
|
|
444
|
-
// Tests that do not exercise narration keep the default narrator out of
|
|
445
|
-
// the way: a null narrator never generates or speaks.
|
|
446
|
-
progressNarrator:
|
|
447
|
-
opts?.progressNarrator === undefined ? null : opts.progressNarrator,
|
|
448
|
-
progressConfig: opts?.progressConfig,
|
|
449
406
|
});
|
|
450
407
|
return { session, relay: transport, controller };
|
|
451
408
|
}
|
|
@@ -549,6 +506,35 @@ describe("call-controller", () => {
|
|
|
549
506
|
controller.destroy();
|
|
550
507
|
});
|
|
551
508
|
|
|
509
|
+
test("handleCallerUtterance: includes speaker context in voice turn content", async () => {
|
|
510
|
+
mockStartVoiceTurn.mockImplementation(
|
|
511
|
+
async (opts: {
|
|
512
|
+
content: string;
|
|
513
|
+
onTextDelta: (t: string) => void;
|
|
514
|
+
onComplete: () => void;
|
|
515
|
+
}) => {
|
|
516
|
+
expect(opts.content).toContain(
|
|
517
|
+
'[SPEAKER id="speaker-1" label="Aaron" source="provider" confidence="0.91"]',
|
|
518
|
+
);
|
|
519
|
+
expect(opts.content).toContain("Can you summarize this meeting?");
|
|
520
|
+
opts.onTextDelta("Sure, here is a summary.");
|
|
521
|
+
opts.onComplete();
|
|
522
|
+
return { turnId: "run-1", abort: () => {} };
|
|
523
|
+
},
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
const { controller } = setupController();
|
|
527
|
+
|
|
528
|
+
await controller.handleCallerUtterance("Can you summarize this meeting?", {
|
|
529
|
+
speakerId: "speaker-1",
|
|
530
|
+
speakerLabel: "Aaron",
|
|
531
|
+
speakerConfidence: 0.91,
|
|
532
|
+
source: "provider",
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
controller.destroy();
|
|
536
|
+
});
|
|
537
|
+
|
|
552
538
|
test("startInitialGreeting: sends CALL_OPENING content and strips control marker from speech", async () => {
|
|
553
539
|
let turnCount = 0;
|
|
554
540
|
mockStartVoiceTurn.mockImplementation(
|
|
@@ -2301,7 +2287,7 @@ describe("call-controller", () => {
|
|
|
2301
2287
|
|
|
2302
2288
|
// Compute expected digest independently using the same utility
|
|
2303
2289
|
const { computeToolApprovalDigest } =
|
|
2304
|
-
await import("
|
|
2290
|
+
await import("../security/tool-approval-digest.js");
|
|
2305
2291
|
const expectedDigest = computeToolApprovalDigest("send_email", {
|
|
2306
2292
|
subject: "Hello",
|
|
2307
2293
|
to: "bob@example.com",
|
|
@@ -3101,7 +3087,7 @@ describe("call-controller", () => {
|
|
|
3101
3087
|
controller.destroy();
|
|
3102
3088
|
});
|
|
3103
3089
|
|
|
3104
|
-
test("synthesized provider: stays 'processing' during synthesis latency
|
|
3090
|
+
test("synthesized provider: stays 'processing' during synthesis latency, so barge-in cannot abort an inaudible turn", async () => {
|
|
3105
3091
|
const cfg = loadConfig();
|
|
3106
3092
|
cfg.services.tts.provider = "fish-audio";
|
|
3107
3093
|
cfg.services.tts.providers["fish-audio"].referenceId = "fish-ref-123";
|
|
@@ -3142,11 +3128,11 @@ describe("call-controller", () => {
|
|
|
3142
3128
|
await new Promise((r) => setTimeout(r, 20));
|
|
3143
3129
|
|
|
3144
3130
|
// No audio has reached the caller yet (play URL not sent), so the controller
|
|
3145
|
-
// must stay in `processing
|
|
3146
|
-
//
|
|
3147
|
-
// cut the inaudible turn off. See JARVIS-1232.
|
|
3131
|
+
// must stay in `processing` — a barge-in here would abort a turn the caller
|
|
3132
|
+
// cannot hear. See JARVIS-1232.
|
|
3148
3133
|
expect(relay.sentPlayUrls.length).toBe(0);
|
|
3149
3134
|
expect(controller.getState()).toBe("processing");
|
|
3135
|
+
expect(controller.handleBargeIn()).toBe(false);
|
|
3150
3136
|
|
|
3151
3137
|
// Release audio → play URL sent → turn finishes and returns to idle.
|
|
3152
3138
|
releaseChunk?.();
|
|
@@ -3418,7 +3404,7 @@ describe("call-controller", () => {
|
|
|
3418
3404
|
function registerFishAudioSegmentRecorder(opts?: {
|
|
3419
3405
|
onSynthesizeStream?: (
|
|
3420
3406
|
text: string,
|
|
3421
|
-
request: import("
|
|
3407
|
+
request: import("../tts/types.js").TtsSynthesisRequest,
|
|
3422
3408
|
onChunk: (chunk: Buffer) => void,
|
|
3423
3409
|
) => Promise<void>;
|
|
3424
3410
|
}): { synthesizedTexts: string[] } {
|
|
@@ -4234,415 +4220,6 @@ describe("call-controller", () => {
|
|
|
4234
4220
|
});
|
|
4235
4221
|
});
|
|
4236
4222
|
|
|
4237
|
-
// ── Triage-and-escalate routing ─────────────────────────────────────
|
|
4238
|
-
|
|
4239
|
-
describe("triage-and-escalate routing", () => {
|
|
4240
|
-
interface LegOpts {
|
|
4241
|
-
content: string;
|
|
4242
|
-
routingLeg?: string;
|
|
4243
|
-
spokenEscalationBridge?: string;
|
|
4244
|
-
launchedAtMs?: number;
|
|
4245
|
-
voiceTelemetry?: { sessionId: string; entry?: string };
|
|
4246
|
-
callbacks?: {
|
|
4247
|
-
persisted_user_message_id?: (id: string) => void;
|
|
4248
|
-
persisted_assistant_message_id?: (id: string) => void;
|
|
4249
|
-
tool_use_start?: (
|
|
4250
|
-
toolName: string,
|
|
4251
|
-
detail?: { toolUseId?: string },
|
|
4252
|
-
) => void;
|
|
4253
|
-
tool_result?: (event: {
|
|
4254
|
-
toolName: string;
|
|
4255
|
-
toolUseId?: string;
|
|
4256
|
-
isError?: boolean;
|
|
4257
|
-
resultPreview: string;
|
|
4258
|
-
}) => void;
|
|
4259
|
-
};
|
|
4260
|
-
onTextDelta: (t: string) => void;
|
|
4261
|
-
onComplete: () => void;
|
|
4262
|
-
signal?: AbortSignal;
|
|
4263
|
-
}
|
|
4264
|
-
|
|
4265
|
-
/**
|
|
4266
|
-
* Script the bridge one leg per call: the front-door leg's tokens first,
|
|
4267
|
-
* then the escalated leg's. Records every call's options and which legs
|
|
4268
|
-
* the controller aborted.
|
|
4269
|
-
*/
|
|
4270
|
-
function scriptLegs(
|
|
4271
|
-
legs: string[][],
|
|
4272
|
-
beforeComplete?: (opts: LegOpts, index: number) => void,
|
|
4273
|
-
) {
|
|
4274
|
-
const calls: LegOpts[] = [];
|
|
4275
|
-
const aborted: number[] = [];
|
|
4276
|
-
mockStartVoiceTurn.mockImplementation(async (opts: LegOpts) => {
|
|
4277
|
-
const index = calls.length;
|
|
4278
|
-
calls.push(opts);
|
|
4279
|
-
for (const token of legs[index] ?? []) {
|
|
4280
|
-
opts.onTextDelta(token);
|
|
4281
|
-
}
|
|
4282
|
-
beforeComplete?.(opts, index);
|
|
4283
|
-
opts.onComplete();
|
|
4284
|
-
return { turnId: `run-${index}`, abort: () => aborted.push(index) };
|
|
4285
|
-
});
|
|
4286
|
-
return { calls, aborted };
|
|
4287
|
-
}
|
|
4288
|
-
|
|
4289
|
-
function spokenText(relay: MockTransport): string {
|
|
4290
|
-
return relay.sentTokens
|
|
4291
|
-
.filter((t) => !t.last)
|
|
4292
|
-
.map((t) => t.token)
|
|
4293
|
-
.join("");
|
|
4294
|
-
}
|
|
4295
|
-
|
|
4296
|
-
function assistantSpokePayload(sessionId: string): Record<string, unknown> {
|
|
4297
|
-
const spoke = getCallEvents(sessionId).filter(
|
|
4298
|
-
(e) => e.eventType === "assistant_spoke",
|
|
4299
|
-
);
|
|
4300
|
-
expect(spoke.length).toBe(1);
|
|
4301
|
-
return JSON.parse(spoke[0].payloadJson) as Record<string, unknown>;
|
|
4302
|
-
}
|
|
4303
|
-
|
|
4304
|
-
test("a front-door answer runs one leg and is spoken verbatim", async () => {
|
|
4305
|
-
const { calls, aborted } = scriptLegs([["Hello", " there."]]);
|
|
4306
|
-
const { session, relay, controller } = setupController();
|
|
4307
|
-
|
|
4308
|
-
await controller.handleCallerUtterance("Hi");
|
|
4309
|
-
|
|
4310
|
-
expect(calls.length).toBe(1);
|
|
4311
|
-
expect(calls[0].routingLeg).toBe("front-door");
|
|
4312
|
-
expect(calls[0].content).toBe("Hi");
|
|
4313
|
-
expect(calls[0].spokenEscalationBridge).toBeUndefined();
|
|
4314
|
-
expect(aborted).toEqual([]);
|
|
4315
|
-
expect(spokenText(relay)).toContain("Hello there.");
|
|
4316
|
-
expect(assistantSpokePayload(session.id).text).toBe("Hello there.");
|
|
4317
|
-
|
|
4318
|
-
controller.destroy();
|
|
4319
|
-
});
|
|
4320
|
-
|
|
4321
|
-
test("an escalate verdict speaks the holding phrase, aborts the front-door leg, and continues on the escalated leg", async () => {
|
|
4322
|
-
const { calls, aborted } = scriptLegs([
|
|
4323
|
-
// The verdict token may arrive split across deltas; the leading
|
|
4324
|
-
// "[" is held until it classifies.
|
|
4325
|
-
["[", "1] Let me check", " that.", " Ignored past the cap."],
|
|
4326
|
-
["The answer is 42."],
|
|
4327
|
-
]);
|
|
4328
|
-
const { session, relay, controller } = setupController();
|
|
4329
|
-
|
|
4330
|
-
await controller.handleCallerUtterance("What is six times seven?");
|
|
4331
|
-
|
|
4332
|
-
expect(calls.length).toBe(2);
|
|
4333
|
-
expect(calls[0].routingLeg).toBe("front-door");
|
|
4334
|
-
expect(calls[1].routingLeg).toBe("escalated");
|
|
4335
|
-
expect(calls[1].content).toBe(ESCALATION_CONTINUATION_CONTENT);
|
|
4336
|
-
expect(calls[1].spokenEscalationBridge).toBe("Let me check that.");
|
|
4337
|
-
expect(aborted).toEqual([0]);
|
|
4338
|
-
|
|
4339
|
-
const spoken = spokenText(relay);
|
|
4340
|
-
expect(spoken).toContain("Let me check that.");
|
|
4341
|
-
expect(spoken).toContain("The answer is 42.");
|
|
4342
|
-
expect(spoken.indexOf("Let me check that.")).toBeLessThan(
|
|
4343
|
-
spoken.indexOf("The answer is 42."),
|
|
4344
|
-
);
|
|
4345
|
-
expect(spoken).not.toContain("[1]");
|
|
4346
|
-
expect(spoken).not.toContain("Ignored past the cap.");
|
|
4347
|
-
expect(assistantSpokePayload(session.id).text).toBe(
|
|
4348
|
-
"Let me check that. The answer is 42.",
|
|
4349
|
-
);
|
|
4350
|
-
// One end-of-turn signal for the whole turn, after the escalated leg.
|
|
4351
|
-
expect(relay.sentTokens.filter((t) => t.last).length).toBe(1);
|
|
4352
|
-
|
|
4353
|
-
controller.destroy();
|
|
4354
|
-
});
|
|
4355
|
-
|
|
4356
|
-
test("a bare escalate verdict speaks the canned bridge, audio-only", async () => {
|
|
4357
|
-
const { calls } = scriptLegs([["[1]"], ["Forty-two."]]);
|
|
4358
|
-
const { session, relay, controller } = setupController();
|
|
4359
|
-
|
|
4360
|
-
await controller.handleCallerUtterance("What is six times seven?");
|
|
4361
|
-
|
|
4362
|
-
expect(calls.length).toBe(2);
|
|
4363
|
-
expect(calls[1].spokenEscalationBridge).toBe(FALLBACK_ESCALATION_BRIDGE);
|
|
4364
|
-
expect(spokenText(relay)).toContain(FALLBACK_ESCALATION_BRIDGE);
|
|
4365
|
-
// The canned bridge is not the model's speech: the turn's text (and the
|
|
4366
|
-
// transcript row the bridge keeps) carry only the escalated answer.
|
|
4367
|
-
expect(assistantSpokePayload(session.id).text).toBe("Forty-two.");
|
|
4368
|
-
|
|
4369
|
-
controller.destroy();
|
|
4370
|
-
});
|
|
4371
|
-
|
|
4372
|
-
test("a leg cancelled mid-bridge never spawns the escalated leg", async () => {
|
|
4373
|
-
const calls: LegOpts[] = [];
|
|
4374
|
-
let releaseLeg: () => void = () => {};
|
|
4375
|
-
mockStartVoiceTurn.mockImplementation(async (opts: LegOpts) => {
|
|
4376
|
-
calls.push(opts);
|
|
4377
|
-
opts.onTextDelta("[1] Let me");
|
|
4378
|
-
await new Promise<void>((resolve) => {
|
|
4379
|
-
releaseLeg = resolve;
|
|
4380
|
-
opts.signal?.addEventListener("abort", () => resolve());
|
|
4381
|
-
});
|
|
4382
|
-
// The bridge reports a cancelled leg's completion too.
|
|
4383
|
-
opts.onComplete();
|
|
4384
|
-
return { turnId: "run-cancelled", abort: () => releaseLeg() };
|
|
4385
|
-
});
|
|
4386
|
-
const { relay, controller } = setupController();
|
|
4387
|
-
|
|
4388
|
-
const turnPromise = controller.handleCallerUtterance("Hi");
|
|
4389
|
-
for (let i = 0; i < 10; i++) {
|
|
4390
|
-
await Promise.resolve();
|
|
4391
|
-
}
|
|
4392
|
-
expect(calls.length).toBe(1);
|
|
4393
|
-
|
|
4394
|
-
controller.handleInterrupt();
|
|
4395
|
-
await turnPromise;
|
|
4396
|
-
|
|
4397
|
-
expect(calls.length).toBe(1);
|
|
4398
|
-
expect(spokenText(relay)).toBe("");
|
|
4399
|
-
|
|
4400
|
-
controller.destroy();
|
|
4401
|
-
});
|
|
4402
|
-
|
|
4403
|
-
test("passes the launch time, phone telemetry, and callbacks to the bridge", async () => {
|
|
4404
|
-
const before = Date.now();
|
|
4405
|
-
const { calls } = scriptLegs([["Done."]], (opts) => {
|
|
4406
|
-
opts.callbacks?.persisted_user_message_id?.("user-row-1");
|
|
4407
|
-
opts.callbacks?.tool_use_start?.("web_search", { toolUseId: "tu-1" });
|
|
4408
|
-
opts.callbacks?.tool_result?.({
|
|
4409
|
-
toolName: "web_search",
|
|
4410
|
-
toolUseId: "tu-1",
|
|
4411
|
-
isError: false,
|
|
4412
|
-
resultPreview: "three results",
|
|
4413
|
-
});
|
|
4414
|
-
opts.callbacks?.persisted_assistant_message_id?.("assistant-row-1");
|
|
4415
|
-
});
|
|
4416
|
-
const { session, controller } = setupController();
|
|
4417
|
-
|
|
4418
|
-
await controller.handleCallerUtterance("Look that up");
|
|
4419
|
-
|
|
4420
|
-
expect(calls.length).toBe(1);
|
|
4421
|
-
expect(calls[0].launchedAtMs).toBeGreaterThanOrEqual(before);
|
|
4422
|
-
expect(calls[0].launchedAtMs).toBeLessThanOrEqual(Date.now());
|
|
4423
|
-
expect(calls[0].voiceTelemetry).toEqual({
|
|
4424
|
-
sessionId: session.id,
|
|
4425
|
-
entry: "phone_inbound",
|
|
4426
|
-
});
|
|
4427
|
-
|
|
4428
|
-
const events = getCallEvents(session.id);
|
|
4429
|
-
const started = events.filter((e) => e.eventType === "tool_use_started");
|
|
4430
|
-
expect(started.length).toBe(1);
|
|
4431
|
-
expect(JSON.parse(started[0].payloadJson)).toEqual({
|
|
4432
|
-
toolName: "web_search",
|
|
4433
|
-
toolUseId: "tu-1",
|
|
4434
|
-
});
|
|
4435
|
-
const completed = events.filter(
|
|
4436
|
-
(e) => e.eventType === "tool_use_completed",
|
|
4437
|
-
);
|
|
4438
|
-
expect(completed.length).toBe(1);
|
|
4439
|
-
expect(JSON.parse(completed[0].payloadJson)).toEqual({
|
|
4440
|
-
toolName: "web_search",
|
|
4441
|
-
toolUseId: "tu-1",
|
|
4442
|
-
isError: false,
|
|
4443
|
-
resultPreview: "three results",
|
|
4444
|
-
});
|
|
4445
|
-
expect(assistantSpokePayload(session.id)).toEqual({
|
|
4446
|
-
text: "Done.",
|
|
4447
|
-
userMessageId: "user-row-1",
|
|
4448
|
-
assistantMessageId: "assistant-row-1",
|
|
4449
|
-
});
|
|
4450
|
-
|
|
4451
|
-
controller.destroy();
|
|
4452
|
-
});
|
|
4453
|
-
|
|
4454
|
-
test("the escalated leg's hidden continuation row is not the caller's utterance", async () => {
|
|
4455
|
-
const { calls } = scriptLegs(
|
|
4456
|
-
[["[1] One moment."], ["Here you go."]],
|
|
4457
|
-
(opts, index) => {
|
|
4458
|
-
opts.callbacks?.persisted_user_message_id?.(`user-row-${index}`);
|
|
4459
|
-
opts.callbacks?.persisted_assistant_message_id?.(
|
|
4460
|
-
`assistant-row-${index}`,
|
|
4461
|
-
);
|
|
4462
|
-
},
|
|
4463
|
-
);
|
|
4464
|
-
const { session, controller } = setupController();
|
|
4465
|
-
|
|
4466
|
-
await controller.handleCallerUtterance("Hi");
|
|
4467
|
-
|
|
4468
|
-
expect(calls.length).toBe(2);
|
|
4469
|
-
expect(assistantSpokePayload(session.id)).toEqual({
|
|
4470
|
-
text: "One moment. Here you go.",
|
|
4471
|
-
userMessageId: "user-row-0",
|
|
4472
|
-
assistantMessageId: "assistant-row-1",
|
|
4473
|
-
});
|
|
4474
|
-
|
|
4475
|
-
controller.destroy();
|
|
4476
|
-
});
|
|
4477
|
-
|
|
4478
|
-
test("native route: a canned bridge in a language the table lacks goes out as system copy", async () => {
|
|
4479
|
-
scriptLegs([["[1]"], ["Forty-two."]]);
|
|
4480
|
-
const { relay, controller } = setupController(undefined, {
|
|
4481
|
-
resolveSynthesisLanguage: () => "ko",
|
|
4482
|
-
});
|
|
4483
|
-
|
|
4484
|
-
await controller.handleCallerUtterance("What is six times seven?");
|
|
4485
|
-
|
|
4486
|
-
// The English fallback must not ride the caller's Korean hint.
|
|
4487
|
-
expect(relay.systemCopyTokens).toEqual([
|
|
4488
|
-
`${FALLBACK_ESCALATION_BRIDGE} `,
|
|
4489
|
-
]);
|
|
4490
|
-
expect(spokenText(relay)).toContain("Forty-two.");
|
|
4491
|
-
|
|
4492
|
-
controller.destroy();
|
|
4493
|
-
});
|
|
4494
|
-
|
|
4495
|
-
test("native route: a localized canned bridge rides the caller's language like model text", async () => {
|
|
4496
|
-
scriptLegs([["[1]"], ["Cuarenta y dos."]]);
|
|
4497
|
-
const { relay, controller } = setupController(undefined, {
|
|
4498
|
-
resolveSynthesisLanguage: () => "es",
|
|
4499
|
-
});
|
|
4500
|
-
|
|
4501
|
-
await controller.handleCallerUtterance("¿Cuánto es seis por siete?");
|
|
4502
|
-
|
|
4503
|
-
expect(relay.systemCopyTokens).toEqual([]);
|
|
4504
|
-
expect(spokenText(relay)).toContain(
|
|
4505
|
-
FALLBACK_ESCALATION_BRIDGE_BY_LANGUAGE.es,
|
|
4506
|
-
);
|
|
4507
|
-
|
|
4508
|
-
controller.destroy();
|
|
4509
|
-
});
|
|
4510
|
-
|
|
4511
|
-
test("synthesized route: the canned bridge carries its own language hint, the answer the caller's", async () => {
|
|
4512
|
-
const requests: Array<{ text: string; language: string | undefined }> =
|
|
4513
|
-
[];
|
|
4514
|
-
registerFishAudioSegmentRecorder({
|
|
4515
|
-
onSynthesizeStream: async (text, request) => {
|
|
4516
|
-
requests.push({ text, language: request.language });
|
|
4517
|
-
},
|
|
4518
|
-
});
|
|
4519
|
-
scriptLegs([["[1]"], ["Forty-two."]]);
|
|
4520
|
-
const { controller } = setupController(undefined, {
|
|
4521
|
-
resolveSynthesisLanguage: () => "ko",
|
|
4522
|
-
});
|
|
4523
|
-
|
|
4524
|
-
await controller.handleCallerUtterance("What is six times seven?");
|
|
4525
|
-
|
|
4526
|
-
expect(requests).toEqual([
|
|
4527
|
-
{ text: FALLBACK_ESCALATION_BRIDGE, language: "en" },
|
|
4528
|
-
{ text: "Forty-two.", language: "ko" },
|
|
4529
|
-
]);
|
|
4530
|
-
|
|
4531
|
-
controller.destroy();
|
|
4532
|
-
});
|
|
4533
|
-
|
|
4534
|
-
test("progress narration speaks into the escalated leg's tool loop, audio-only", async () => {
|
|
4535
|
-
const narrationConfig: VoiceProgressConfig = {
|
|
4536
|
-
enabled: true,
|
|
4537
|
-
opsThreshold: 1,
|
|
4538
|
-
idleIntervalMs: 60_000,
|
|
4539
|
-
maxSilenceMs: 60_000,
|
|
4540
|
-
longOpMs: 15_000,
|
|
4541
|
-
minGapMs: 10,
|
|
4542
|
-
generationTimeoutMs: 1_500,
|
|
4543
|
-
};
|
|
4544
|
-
const narrator: VoiceProgressNarrator = {
|
|
4545
|
-
generateProgressText: async (input) => {
|
|
4546
|
-
expect(input.transcriptSoFar).toBe("What is on my calendar?");
|
|
4547
|
-
expect(input.currentOp?.toolName).toBe("calendar_list");
|
|
4548
|
-
return "Pulling up your calendar now.";
|
|
4549
|
-
},
|
|
4550
|
-
};
|
|
4551
|
-
const { session, relay, controller } = setupController(undefined, {
|
|
4552
|
-
progressNarrator: narrator,
|
|
4553
|
-
progressConfig: narrationConfig,
|
|
4554
|
-
});
|
|
4555
|
-
const calls: LegOpts[] = [];
|
|
4556
|
-
mockStartVoiceTurn.mockImplementation(async (opts: LegOpts) => {
|
|
4557
|
-
const index = calls.length;
|
|
4558
|
-
calls.push(opts);
|
|
4559
|
-
if (index === 0) {
|
|
4560
|
-
opts.onTextDelta("[1] One moment.");
|
|
4561
|
-
opts.onComplete();
|
|
4562
|
-
return { turnId: "run-front", abort: () => {} };
|
|
4563
|
-
}
|
|
4564
|
-
// The escalated leg runs a tool; narration must land while it runs.
|
|
4565
|
-
// The bridge just spoken is a floor holder, so the tool starts after
|
|
4566
|
-
// the (test-short) minimum gap.
|
|
4567
|
-
await new Promise((r) => setTimeout(r, 25));
|
|
4568
|
-
opts.callbacks?.tool_use_start?.("calendar_list", { toolUseId: "c1" });
|
|
4569
|
-
await pollUntil(() =>
|
|
4570
|
-
relay.sentTokens.some((t) =>
|
|
4571
|
-
t.token.includes("Pulling up your calendar now."),
|
|
4572
|
-
),
|
|
4573
|
-
);
|
|
4574
|
-
opts.callbacks?.tool_result?.({
|
|
4575
|
-
toolName: "calendar_list",
|
|
4576
|
-
toolUseId: "c1",
|
|
4577
|
-
resultPreview: "two events",
|
|
4578
|
-
});
|
|
4579
|
-
opts.onTextDelta("You have two events today.");
|
|
4580
|
-
opts.onComplete();
|
|
4581
|
-
return { turnId: "run-escalated", abort: () => {} };
|
|
4582
|
-
});
|
|
4583
|
-
|
|
4584
|
-
await controller.handleCallerUtterance("What is on my calendar?");
|
|
4585
|
-
|
|
4586
|
-
const spoken = spokenText(relay);
|
|
4587
|
-
expect(spoken.indexOf("One moment.")).toBeLessThan(
|
|
4588
|
-
spoken.indexOf("Pulling up your calendar now."),
|
|
4589
|
-
);
|
|
4590
|
-
expect(spoken.indexOf("Pulling up your calendar now.")).toBeLessThan(
|
|
4591
|
-
spoken.indexOf("You have two events today."),
|
|
4592
|
-
);
|
|
4593
|
-
// Narration is audio-only: the turn's recorded text is the model's.
|
|
4594
|
-
expect(assistantSpokePayload(session.id).text).toBe(
|
|
4595
|
-
"One moment. You have two events today.",
|
|
4596
|
-
);
|
|
4597
|
-
|
|
4598
|
-
controller.destroy();
|
|
4599
|
-
});
|
|
4600
|
-
|
|
4601
|
-
test("no narration without a narrator", async () => {
|
|
4602
|
-
const { relay, controller } = setupController(undefined, {
|
|
4603
|
-
progressNarrator: null,
|
|
4604
|
-
progressConfig: {
|
|
4605
|
-
enabled: true,
|
|
4606
|
-
opsThreshold: 1,
|
|
4607
|
-
idleIntervalMs: 60_000,
|
|
4608
|
-
maxSilenceMs: 60_000,
|
|
4609
|
-
longOpMs: 15_000,
|
|
4610
|
-
minGapMs: 10,
|
|
4611
|
-
generationTimeoutMs: 1_500,
|
|
4612
|
-
},
|
|
4613
|
-
});
|
|
4614
|
-
scriptLegs([["Done."]], (opts) => {
|
|
4615
|
-
opts.callbacks?.tool_use_start?.("web_search", { toolUseId: "t1" });
|
|
4616
|
-
opts.callbacks?.tool_result?.({
|
|
4617
|
-
toolName: "web_search",
|
|
4618
|
-
toolUseId: "t1",
|
|
4619
|
-
resultPreview: "ok",
|
|
4620
|
-
});
|
|
4621
|
-
});
|
|
4622
|
-
|
|
4623
|
-
await controller.handleCallerUtterance("Look it up");
|
|
4624
|
-
|
|
4625
|
-
expect(spokenText(relay)).toBe("Done.");
|
|
4626
|
-
|
|
4627
|
-
controller.destroy();
|
|
4628
|
-
});
|
|
4629
|
-
|
|
4630
|
-
test("an outbound call reports the outbound entry", async () => {
|
|
4631
|
-
const { calls } = scriptLegs([["Hello, this is Ava."]]);
|
|
4632
|
-
const { session, controller } = setupController("Book a table for two");
|
|
4633
|
-
|
|
4634
|
-
await controller.startInitialGreeting();
|
|
4635
|
-
|
|
4636
|
-
expect(calls.length).toBe(1);
|
|
4637
|
-
expect(calls[0].voiceTelemetry).toEqual({
|
|
4638
|
-
sessionId: session.id,
|
|
4639
|
-
entry: "phone_outbound",
|
|
4640
|
-
});
|
|
4641
|
-
|
|
4642
|
-
controller.destroy();
|
|
4643
|
-
});
|
|
4644
|
-
});
|
|
4645
|
-
|
|
4646
4223
|
// ── handleBargeIn ───────────────────────────────────────────────────
|
|
4647
4224
|
|
|
4648
4225
|
describe("handleBargeIn", () => {
|
|
@@ -4665,11 +4242,10 @@ describe("call-controller", () => {
|
|
|
4665
4242
|
controller.destroy();
|
|
4666
4243
|
});
|
|
4667
4244
|
|
|
4668
|
-
test("handleBargeIn
|
|
4245
|
+
test("handleBargeIn returns false and does not abort while still processing (no output yet)", async () => {
|
|
4669
4246
|
// Simulate a turn stuck waiting for the processing lock: no tokens
|
|
4670
|
-
// emitted, no completion. The controller
|
|
4671
|
-
//
|
|
4672
|
-
// vouched for it) cuts the thinking turn off before it speaks.
|
|
4247
|
+
// emitted, no completion. The controller must stay in `processing` and
|
|
4248
|
+
// NOT flip to `speaking`, so barge-in can't abort a silent turn.
|
|
4673
4249
|
mockStartVoiceTurn.mockImplementation(
|
|
4674
4250
|
async (opts: {
|
|
4675
4251
|
onTextDelta: (t: string) => void;
|
|
@@ -4694,17 +4270,18 @@ describe("call-controller", () => {
|
|
|
4694
4270
|
|
|
4695
4271
|
const onAccepted = mock(() => {});
|
|
4696
4272
|
const bargeResult = controller.handleBargeIn(onAccepted);
|
|
4697
|
-
expect(bargeResult).toBe(
|
|
4698
|
-
expect(onAccepted).
|
|
4699
|
-
//
|
|
4700
|
-
expect(controller.getState()).toBe("
|
|
4273
|
+
expect(bargeResult).toBe(false);
|
|
4274
|
+
expect(onAccepted).not.toHaveBeenCalled();
|
|
4275
|
+
// Still processing (not aborted), and no interrupt/end-of-turn token sent.
|
|
4276
|
+
expect(controller.getState()).toBe("processing");
|
|
4701
4277
|
const endTokens = relay.sentTokens.filter(
|
|
4702
4278
|
(t) => t.last === true && t.token === "",
|
|
4703
4279
|
);
|
|
4704
4280
|
expect(endTokens.length).toBe(0);
|
|
4705
4281
|
|
|
4706
|
-
|
|
4282
|
+
// Cleanup: abort the pending turn
|
|
4707
4283
|
controller.destroy();
|
|
4284
|
+
await turnPromise.catch(() => {});
|
|
4708
4285
|
});
|
|
4709
4286
|
|
|
4710
4287
|
test("stays in processing until first token, then flips to speaking and barge-in is accepted", async () => {
|
|
@@ -4746,7 +4323,9 @@ describe("call-controller", () => {
|
|
|
4746
4323
|
await Promise.resolve();
|
|
4747
4324
|
}
|
|
4748
4325
|
|
|
4749
|
-
// Before any token: processing (
|
|
4326
|
+
// Before any token: processing, barge-in ignored (turn not aborted).
|
|
4327
|
+
expect(controller.getState()).toBe("processing");
|
|
4328
|
+
expect(controller.handleBargeIn()).toBe(false);
|
|
4750
4329
|
expect(controller.getState()).toBe("processing");
|
|
4751
4330
|
|
|
4752
4331
|
// Release the first token → controller flips to speaking.
|
|
@@ -4764,7 +4343,7 @@ describe("call-controller", () => {
|
|
|
4764
4343
|
await turnPromise.catch(() => {});
|
|
4765
4344
|
});
|
|
4766
4345
|
|
|
4767
|
-
test("buffering transport (media-stream): streamed tokens do not flip to speaking;
|
|
4346
|
+
test("buffering transport (media-stream): streamed tokens do not flip to speaking; barge-in is rejected and the turn still delivers", async () => {
|
|
4768
4347
|
// Simulates the media-stream transport: sendTextToken(.., false)
|
|
4769
4348
|
// only buffers; audio starts later. The controller must stay in
|
|
4770
4349
|
// `processing` until the transport's audio-start signal fires, so
|
|
@@ -4808,17 +4387,17 @@ describe("call-controller", () => {
|
|
|
4808
4387
|
expect(controller.getState()).toBe("processing");
|
|
4809
4388
|
expect(audioStartCallback).not.toBeNull();
|
|
4810
4389
|
|
|
4811
|
-
//
|
|
4812
|
-
|
|
4813
|
-
expect(controller.
|
|
4814
|
-
expect(controller.getState()).toBe("idle");
|
|
4390
|
+
// VAD speech-start mid-generation → barge-in rejected, turn intact.
|
|
4391
|
+
expect(controller.handleBargeIn()).toBe(false);
|
|
4392
|
+
expect(controller.getState()).toBe("processing");
|
|
4815
4393
|
|
|
4816
|
-
|
|
4817
|
-
|
|
4394
|
+
// The turn completes and delivers its end-of-turn signal.
|
|
4395
|
+
releaseTurn();
|
|
4396
|
+
await turnPromise;
|
|
4818
4397
|
const endTokens = relay.sentTokens.filter(
|
|
4819
4398
|
(t) => t.last === true && t.token === "",
|
|
4820
4399
|
);
|
|
4821
|
-
expect(endTokens.length).toBe(
|
|
4400
|
+
expect(endTokens.length).toBe(1);
|
|
4822
4401
|
|
|
4823
4402
|
controller.destroy();
|
|
4824
4403
|
});
|