@vellumai/assistant 0.10.4 → 0.10.5-dev.202607022329.d89bf2f
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 +94 -91
- package/Dockerfile +5 -16
- package/README.md +7 -10
- package/bun.lock +0 -4
- package/docs/architecture/security.md +20 -20
- package/docs/stt-provider-onboarding.md +4 -5
- package/docs/trusted-contact-access.md +7 -7
- package/eslint-rules/cli-no-daemon-internals.js +4 -0
- package/eslint.config.mjs +7 -1
- package/knip.json +0 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +36 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +233 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +40 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -8
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +33 -0
- package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +216 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +91 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +39 -3
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +0 -8
- package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +1 -5
- package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +3 -14
- package/openapi.yaml +1073 -225
- package/package.json +1 -4
- package/scripts/test.sh +35 -13
- package/src/__tests__/access-request-seed-content-blocks.test.ts +60 -4
- package/src/__tests__/acp-session.test.ts +4 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +0 -6
- package/src/__tests__/add-message-preassigned-id.test.ts +56 -0
- package/src/__tests__/agent-loop-set-system-prompt.test.ts +139 -0
- package/src/__tests__/app-conversation-ids-backfill.test.ts +3 -0
- package/src/__tests__/app-source-watcher.test.ts +11 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +148 -3
- package/src/__tests__/assistant-attachments.test.ts +62 -4
- package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -1
- package/src/__tests__/attachments-store-heic-normalize.test.ts +230 -0
- package/src/__tests__/attachments-store.test.ts +3 -0
- package/src/__tests__/background-shell-bash.test.ts +34 -0
- package/src/__tests__/background-shell-host-bash.test.ts +12 -0
- package/src/__tests__/background-tool-registry.test.ts +97 -0
- package/src/__tests__/background-tool-routes.test.ts +72 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/btw-routes.test.ts +57 -3
- package/src/__tests__/call-controller.test.ts +474 -84
- package/src/__tests__/call-domain.test.ts +48 -1
- package/src/__tests__/call-pointer-messages.test.ts +57 -19
- package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +2 -6
- package/src/__tests__/call-routes-http.test.ts +15 -14
- package/src/__tests__/call-setup-flow-invite.test.ts +568 -0
- package/src/__tests__/call-setup-flow-midcall-trust.test.ts +231 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +754 -0
- package/src/__tests__/call-setup-flow-verification.test.ts +925 -0
- package/src/__tests__/call-setup-flow.test.ts +311 -0
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -1
- package/src/__tests__/channel-guardian.test.ts +4 -7
- package/src/__tests__/channel-readiness-service.test.ts +0 -6
- package/src/__tests__/channel-readiness-slack-remote.test.ts +0 -4
- package/src/__tests__/channel-retry-sweep.test.ts +76 -64
- package/src/__tests__/channel-setup-panel-ack.test.ts +184 -0
- package/src/__tests__/clear-all-lexical.test.ts +90 -0
- package/src/__tests__/compactor-media-strip.test.ts +177 -0
- package/src/__tests__/config-loader-backfill.test.ts +5 -5
- package/src/__tests__/config-schema.test.ts +17 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +27 -70
- package/src/__tests__/contacts-relay-reads.test.ts +188 -23
- package/src/__tests__/contacts-tools.test.ts +42 -1
- package/src/__tests__/contacts-write.test.ts +181 -0
- package/src/__tests__/context-search-conversations-source.test.ts +220 -81
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +39 -0
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -1
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -0
- package/src/__tests__/conversation-clear-safety.test.ts +0 -1
- package/src/__tests__/conversation-crud-enabled-plugins.test.ts +60 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -1
- package/src/__tests__/conversation-disk-view.test.ts +1 -0
- package/src/__tests__/conversation-enabledplugins-route.test.ts +184 -0
- package/src/__tests__/conversation-error.test.ts +0 -18
- package/src/__tests__/conversation-fork-crud.test.ts +67 -0
- package/src/__tests__/conversation-history-web-search.test.ts +7 -137
- package/src/__tests__/conversation-initial-prompt.test.ts +110 -0
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +191 -0
- package/src/__tests__/conversation-process-callsite.test.ts +37 -0
- package/src/__tests__/conversation-queue.test.ts +149 -0
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +392 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +84 -0
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +407 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +36 -16
- package/src/__tests__/conversation-skill-tools.test.ts +73 -0
- package/src/__tests__/conversation-wipe.test.ts +20 -5
- package/src/__tests__/credential-prompt-route.test.ts +69 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -3
- package/src/__tests__/db-drop-messages-fts.test.ts +115 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -62
- package/src/__tests__/edit-propagation.test.ts +56 -2
- package/src/__tests__/empty-state-greeting-cache.test.ts +9 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +15 -110
- package/src/__tests__/guardian-expiry-notifier.test.ts +1 -0
- package/src/__tests__/guardian-question-mode.test.ts +57 -0
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
- package/src/__tests__/guardian-wait-controller.test.ts +619 -0
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +33 -2
- package/src/__tests__/heic-fixture.ts +55 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +0 -2
- package/src/__tests__/http-conversation-lineage.test.ts +6 -1
- package/src/__tests__/image-conversion.test.ts +170 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +72 -341
- package/src/__tests__/introduction-card-resolver.test.ts +354 -0
- package/src/__tests__/invite-redeemed-ipc.test.ts +97 -0
- package/src/__tests__/invite-routes-http.test.ts +246 -420
- package/src/__tests__/job-handler-registration-guard.test.ts +104 -0
- package/src/__tests__/lexical-index-dual-write.test.ts +426 -0
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +2 -2
- package/src/__tests__/list-messages-attachments.test.ts +159 -0
- package/src/__tests__/list-messages-background-tool-completion.test.ts +141 -0
- package/src/__tests__/list-messages-queued.test.ts +25 -0
- package/src/__tests__/managed-store.test.ts +129 -1
- package/src/__tests__/media-stream-audio-transcode.test.ts +113 -0
- package/src/__tests__/media-stream-output.test.ts +350 -19
- package/src/__tests__/media-stream-server-integration.test.ts +943 -169
- package/src/__tests__/media-stream-stt-session.test.ts +498 -1
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +2 -2
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/mtime-cache.test.ts +19 -0
- package/src/__tests__/non-member-access-request.test.ts +349 -3
- package/src/__tests__/notification-decision-fallback.test.ts +104 -8
- package/src/__tests__/notification-decision-strategy.test.ts +57 -8
- package/src/__tests__/onboarding-template-contract.test.ts +27 -43
- package/src/__tests__/openai-provider.test.ts +33 -0
- package/src/__tests__/openai-responses-provider.test.ts +40 -0
- package/src/__tests__/persistence-layering-guard.test.ts +29 -20
- package/src/__tests__/persona-resolver.test.ts +56 -2
- package/src/__tests__/personal-memory-auth-bypass.test.ts +62 -0
- package/src/__tests__/phone.test.ts +1 -15
- package/src/__tests__/plugin-bootstrap.test.ts +25 -196
- package/src/__tests__/plugin-disabled-state.test.ts +33 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +227 -0
- package/src/__tests__/plugin-event-hub-facade.test.ts +323 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +156 -42
- package/src/__tests__/plugin-injector-contribution.test.ts +4 -20
- package/src/__tests__/plugin-pipeline.test.ts +63 -1
- package/src/__tests__/plugin-route-contribution.test.ts +62 -115
- package/src/__tests__/plugin-tool-contribution.test.ts +0 -22
- package/src/__tests__/plugin-types.test.ts +0 -1
- package/src/__tests__/pointer-conversation-trust.test.ts +38 -6
- package/src/__tests__/pre-model-call-sanitize.test.ts +1 -1
- package/src/__tests__/prune-old-tool-invocations-job.test.ts +97 -0
- package/src/__tests__/public-ingress-urls.test.ts +0 -48
- package/src/__tests__/qdrant-collection-migration.test.ts +145 -2
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +0 -1
- package/src/__tests__/resolve-trust-class.test.ts +28 -10
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +137 -0
- package/src/__tests__/schedule-retry.test.ts +38 -14
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +3 -3
- package/src/__tests__/schedule-routes.test.ts +14 -6
- package/src/__tests__/schedule-store.test.ts +3 -1
- package/src/__tests__/scheduler-disk-pressure.test.ts +17 -6
- package/src/__tests__/scheduler-recurrence.test.ts +57 -107
- package/src/__tests__/scheduler-reuse-conversation.test.ts +51 -46
- package/src/__tests__/scheduler-wake.test.ts +13 -23
- package/src/__tests__/scheduler-worker-standdown.test.ts +220 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/shell-observability.test.ts +1 -0
- package/src/__tests__/skill-boundary-guard.test.ts +3 -4
- package/src/__tests__/skill-load-plugin-scope.test.ts +238 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +2 -0
- package/src/__tests__/skill-script-runner-host.test.ts +53 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +5 -2
- package/src/__tests__/skill-tool-factory.test.ts +73 -1
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -14
- package/src/__tests__/slack-inbound-verification.test.ts +36 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +89 -2
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +51 -1
- package/src/__tests__/stt-hints.test.ts +2 -256
- package/src/__tests__/subagent-allowlist-validation.test.ts +47 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +72 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +14 -2
- package/src/__tests__/subagent-persistence.test.ts +122 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
- package/src/__tests__/subagent-terminal-message.test.ts +99 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -1
- package/src/__tests__/system-prompt.test.ts +17 -118
- package/src/__tests__/task-scheduler.test.ts +68 -89
- package/src/__tests__/telephony-credential-preflight.test.ts +312 -0
- package/src/__tests__/telephony-tts-guard.test.ts +538 -0
- package/src/__tests__/title-generate-hook.test.ts +16 -0
- package/src/__tests__/tool-approval-handler.test.ts +87 -1
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +45 -20
- package/src/__tests__/tool-grant-request-escalation.test.ts +39 -0
- package/src/__tests__/trust-context-guards.test.ts +21 -10
- package/src/__tests__/trusted-contact-multichannel.test.ts +4 -0
- package/src/__tests__/twilio-provider.test.ts +17 -17
- package/src/__tests__/twilio-routes-twiml.test.ts +6 -543
- package/src/__tests__/twilio-routes.test.ts +257 -387
- package/src/__tests__/twilio-validation.test.ts +1 -1
- package/src/__tests__/unified-turn-context-client-os.test.ts +45 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +5 -0
- package/src/__tests__/voice-approval-resolver-relay.test.ts +150 -0
- package/src/__tests__/voice-ingress-preflight.test.ts +61 -0
- package/src/__tests__/voice-quality.test.ts +2 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -7
- package/src/__tests__/voice-session-bridge-processing-wait.test.ts +27 -0
- package/src/__tests__/voice-session-bridge.test.ts +1 -10
- package/src/__tests__/workspace-git-service.test.ts +1 -1
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -0
- package/src/__tests__/workspace-migration-118-seed-now-md.test.ts +120 -0
- package/src/__tests__/workspace-migration-120-revise-onboarding-threads.test.ts +175 -0
- package/src/__tests__/workspace-migration-121-seed-default-user-guardrails.test.ts +118 -0
- package/src/__tests__/workspace-migration-122-relocate-default-user-boundary.test.ts +119 -0
- package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +191 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +235 -241
- package/src/acp/__tests__/prepare-agent-env.test.ts +0 -97
- package/src/acp/__tests__/session-manager-persistence.test.ts +2 -0
- package/src/acp/__tests__/session-manager-resume.test.ts +8 -0
- package/src/acp/__tests__/session-manager-usage.test.ts +6 -0
- package/src/acp/__tests__/session-manager.test.ts +6 -0
- package/src/acp/agent-process.test.ts +116 -0
- package/src/acp/agent-process.ts +71 -0
- package/src/acp/auto-install.ts +12 -8
- package/src/acp/failure-error.test.ts +109 -0
- package/src/acp/failure-error.ts +111 -0
- package/src/acp/prepare-agent-env.ts +0 -12
- package/src/acp/resolve-agent.test.ts +6 -45
- package/src/acp/resolve-agent.ts +2 -4
- package/src/acp/session-manager.test.ts +312 -0
- package/src/acp/session-manager.ts +102 -46
- package/src/agent/ax-tree-compaction.test.ts +4 -1
- package/src/agent/image-optimize.ts +17 -127
- package/src/agent/loop.ts +45 -192
- package/src/api/events/background-tool-completed.test.ts +51 -0
- package/src/api/events/background-tool-completed.ts +29 -0
- package/src/api/events/background-tool-started.test.ts +36 -0
- package/src/api/events/background-tool-started.ts +28 -0
- package/src/api/events/conversation-error.ts +0 -1
- package/src/api/events/open-panel.ts +33 -0
- package/src/api/events/subagent-status-changed.ts +4 -1
- package/src/api/index.ts +19 -0
- package/src/api/responses/conversation-message.ts +34 -0
- package/src/approvals/guardian-decision-primitive.ts +74 -24
- package/src/approvals/guardian-request-resolvers.ts +450 -122
- package/src/apps/app-store.ts +1 -0
- package/src/apps/shared-app-links-store.ts +1 -0
- package/src/avatar/identity-avatar.ts +3 -7
- package/src/background-wake/wake-intent-hooks.test.ts +1 -1
- package/src/calls/__tests__/fish-audio-client.test.ts +73 -0
- package/src/calls/__tests__/gateway-invite-reader.test.ts +170 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +126 -102
- package/src/calls/call-controller.ts +134 -48
- package/src/calls/call-domain.ts +18 -42
- package/src/calls/call-pointer-messages.ts +51 -67
- package/src/calls/call-setup-flow-types.ts +76 -0
- package/src/calls/call-setup-flow.ts +1743 -0
- package/src/calls/call-speech-output.ts +87 -23
- package/src/calls/call-store.ts +4 -0
- package/src/calls/call-transport.ts +22 -36
- package/src/calls/fish-audio-client.ts +3 -0
- package/src/calls/gateway-invite-reader.ts +98 -0
- package/src/calls/guardian-wait-controller.ts +451 -0
- package/src/calls/media-stream-audio-transcode.ts +82 -2
- package/src/calls/media-stream-output.ts +142 -36
- package/src/calls/media-stream-server.ts +297 -178
- package/src/calls/media-stream-stt-session.ts +393 -50
- package/src/calls/media-turn-detector.ts +4 -6
- package/src/calls/relay-access-wait.ts +3 -4
- package/src/calls/relay-setup-router.ts +22 -43
- package/src/calls/relay-verification.ts +17 -24
- package/src/calls/resolve-call-tts-provider.ts +96 -29
- package/src/calls/stt-hints.ts +24 -87
- package/src/calls/telephony-credential-preflight.ts +159 -0
- package/src/calls/telephony-tts-capability.ts +183 -0
- package/src/calls/tts-call-strategy.ts +18 -20
- package/src/calls/twilio-provider.ts +3 -2
- package/src/calls/twilio-routes.ts +47 -351
- package/src/calls/types.ts +5 -3
- package/src/calls/voice-ingress-preflight.ts +9 -0
- package/src/calls/voice-quality.ts +13 -18
- package/src/calls/voice-session-bridge.ts +25 -35
- package/src/channels/__tests__/types.test.ts +28 -0
- package/src/channels/channel-verification-sessions.ts +7 -2
- package/src/channels/config.ts +0 -42
- package/src/channels/slack-thread-store.ts +12 -0
- package/src/channels/types.ts +23 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
- package/src/cli/commands/__tests__/schedules.test.ts +83 -34
- package/src/cli/commands/__tests__/status.test.ts +42 -2
- package/src/cli/commands/contacts.ts +2 -2
- package/src/cli/commands/credentials.ts +17 -0
- package/src/cli/commands/monitoring.ts +191 -0
- package/src/cli/commands/schedules-worker.ts +175 -0
- package/src/cli/commands/schedules.ts +169 -43
- package/src/cli/commands/status.ts +12 -1
- package/src/cli/commands/tools.ts +5 -1
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +1 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +29 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +7 -0
- package/src/cli/lib/search-plugins.ts +6 -0
- package/src/cli/lib/toggle-plugin.ts +21 -2
- package/src/cli/program.ts +3 -1
- package/src/config/__tests__/deployment-context-defaults.test.ts +23 -0
- package/src/config/acp-defaults.test.ts +0 -10
- package/src/config/acp-defaults.ts +0 -6
- package/src/config/bundled-skills/acp/SKILL.md +13 -45
- package/src/config/bundled-skills/acp/TOOLS.json +3 -3
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -3
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +9 -9
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +1 -1
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -9
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +10 -7
- package/src/config/bundled-skills/skill-management/TOOLS.json +10 -0
- package/src/config/call-site-defaults.ts +0 -2
- package/src/config/feature-flag-registry.json +16 -8
- package/src/config/loader.ts +11 -0
- package/src/config/schema.ts +9 -1
- package/src/config/schemas/__tests__/memory-v2.test.ts +2 -2
- package/src/config/schemas/__tests__/memory-v3.test.ts +64 -0
- package/src/config/schemas/call-site-catalog.ts +0 -12
- package/src/config/schemas/calls.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +1 -1
- package/src/config/schemas/llm.ts +0 -2
- package/src/config/schemas/memory-v2.ts +1 -1
- package/src/config/schemas/memory-v3.ts +82 -0
- package/src/config/schemas/monitoring.ts +61 -0
- package/src/config/schemas/schedules.ts +35 -0
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills-plugin-filter.test.ts +96 -0
- package/src/config/skills.ts +26 -0
- package/src/contacts/__tests__/member-write-relay.test.ts +156 -4
- package/src/contacts/canonical-guardian-store.ts +5 -0
- package/src/contacts/contact-store.ts +81 -35
- package/src/contacts/contacts-write.ts +50 -25
- package/src/contacts/member-write-relay.ts +115 -31
- package/src/contacts/types.ts +0 -8
- package/src/context/compactor.ts +29 -15
- package/src/context/outbound-sanitize.ts +202 -0
- package/src/daemon/__tests__/abort-null-controller.test.ts +167 -0
- package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +122 -0
- package/src/daemon/__tests__/embedding-reconcile.test.ts +99 -16
- package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +107 -0
- package/src/daemon/abort-watchdog.ts +7 -0
- package/src/daemon/app-source-watcher.ts +9 -0
- package/src/daemon/assistant-attachments.ts +42 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +256 -147
- package/src/daemon/conversation-agent-loop.ts +61 -68
- package/src/daemon/conversation-error.ts +1 -15
- package/src/daemon/conversation-history.ts +32 -236
- package/src/daemon/conversation-initial-prompt.ts +77 -0
- package/src/daemon/conversation-lifecycle.ts +25 -1
- package/src/daemon/conversation-messaging.ts +2 -2
- package/src/daemon/conversation-plugin-scope.ts +38 -0
- package/src/daemon/conversation-process.ts +50 -14
- package/src/daemon/conversation-runtime-assembly.ts +36 -6
- package/src/daemon/conversation-skill-tools.ts +19 -3
- package/src/daemon/conversation-store.ts +3 -3
- package/src/daemon/conversation-surfaces.ts +171 -0
- package/src/daemon/conversation-tool-setup.test.ts +95 -0
- package/src/daemon/conversation-tool-setup.ts +116 -1
- package/src/daemon/conversation-turn-finalize.ts +211 -0
- package/src/daemon/conversation.ts +92 -1
- package/src/daemon/disk-pressure-policy.ts +2 -1
- package/src/daemon/event-loop-watchdog.ts +22 -6
- package/src/daemon/external-plugins-bootstrap.ts +47 -134
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +26 -14
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +27 -14
- package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -5
- package/src/daemon/handlers/config-a2a.ts +7 -14
- package/src/daemon/handlers/config-channels.ts +2 -10
- package/src/daemon/handlers/conversation-history.ts +3 -1
- package/src/daemon/handlers/conversations.ts +0 -52
- package/src/daemon/handlers/shared.ts +4 -24
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +59 -472
- package/src/daemon/message-protocol.ts +3 -3
- package/src/daemon/message-provenance.ts +12 -14
- package/src/daemon/message-types/background-tools.ts +28 -0
- package/src/daemon/message-types/contacts.ts +2 -10
- package/src/daemon/message-types/conversations.ts +10 -0
- package/src/daemon/message-types/integrations.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +1 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/pointer-conversation-trust.ts +23 -9
- package/src/daemon/pointer-turn-runner.ts +174 -0
- package/src/daemon/providers-setup.ts +16 -0
- package/src/daemon/shutdown-handlers.ts +139 -47
- package/src/daemon/skill-memory-refresh.ts +2 -2
- package/src/daemon/sqlite-corruption-watchdog.test.ts +217 -0
- package/src/daemon/sqlite-corruption-watchdog.ts +155 -0
- package/src/daemon/tool-setup-types.ts +8 -0
- package/src/daemon/trust-context.ts +21 -6
- package/src/daemon/turn-latency-tracker.test.ts +93 -0
- package/src/daemon/turn-latency-tracker.ts +38 -0
- package/src/daemon/wake-conversation-ops.ts +10 -8
- package/src/documents/document-comments-store.test.ts +7 -1
- package/src/documents/document-comments-store.ts +8 -0
- package/src/documents/document-store.ts +15 -0
- package/src/export/__tests__/transcript-formatter.test.ts +11 -0
- package/src/export/transcript-formatter.ts +49 -67
- package/src/home/__tests__/feed-writer.test.ts +247 -1
- package/src/home/conversation-starter-checkpoints.ts +1 -0
- package/src/home/feed-writer.ts +61 -0
- package/src/home/job-handlers/conversation-starters.ts +1 -0
- package/src/hooks/hook-loader.ts +12 -0
- package/src/hooks/registry.ts +32 -4
- package/src/inbound/public-ingress-urls.ts +0 -17
- package/src/ipc/__tests__/contacts-info-ipc.test.ts +285 -0
- package/src/ipc/assistant-server.ts +28 -7
- package/src/ipc/routes/__tests__/contacts-mirror-apply-atomicity.test.ts +62 -0
- package/src/ipc/routes/__tests__/contacts-mirror-apply.test.ts +272 -0
- package/src/ipc/routes/__tests__/contacts-mirror-ipc-routes.test.ts +322 -0
- package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +60 -4
- package/src/ipc/routes/contacts-info-ipc-routes.ts +239 -0
- package/src/ipc/routes/contacts-mirror-ipc-routes.ts +202 -0
- package/src/ipc/routes/db-proxy.ts +5 -2
- package/src/ipc/routes/invite-ipc-routes.ts +53 -39
- package/src/jobs/worker.ts +9 -2
- package/src/live-voice/__tests__/live-voice-archive.test.ts +10 -1
- package/src/live-voice/live-voice-archive.ts +5 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +22 -6
- package/src/messaging/providers/channel-transport.ts +6 -0
- package/src/messaging/providers/index.ts +3 -0
- package/src/messaging/providers/slack/api.test.ts +49 -1
- package/src/messaging/providers/slack/api.ts +110 -0
- package/src/messaging/providers/slack/send.test.ts +85 -0
- package/src/messaging/providers/slack/send.ts +100 -28
- package/src/messaging/providers/slack/transport.ts +7 -13
- package/src/messaging/providers/whatsapp/send.ts +29 -9
- package/src/monitoring/__tests__/control.test.ts +235 -0
- package/src/monitoring/__tests__/sample-ring-buffer.test.ts +96 -0
- package/src/monitoring/control.ts +105 -0
- package/src/monitoring/resource-sampler.ts +258 -0
- package/src/monitoring/sample-ring-buffer.ts +108 -0
- package/src/monitoring/worker.ts +94 -0
- package/src/notifications/README.md +1 -1
- package/src/notifications/access-request-copy.ts +94 -23
- package/src/notifications/adapters/slack.ts +89 -21
- package/src/notifications/approval-card-builder.ts +33 -18
- package/src/notifications/approval-card-data.ts +48 -15
- package/src/notifications/broadcaster.ts +25 -16
- package/src/notifications/decision-engine.ts +131 -32
- package/src/notifications/guardian-question-mode.ts +58 -10
- package/src/permissions/approval-policy.ts +8 -1
- package/src/permissions/checker.ts +2 -0
- package/src/permissions/gateway-threshold-reader.ts +7 -6
- package/src/permissions/ipc-risk-types.ts +1 -0
- package/src/permissions/secret-prompter.ts +11 -3
- package/src/permissions/types.ts +2 -2
- package/src/persistence/__tests__/conversation-queries-search.test.ts +397 -0
- package/src/persistence/__tests__/conversation-search-lexical.test.ts +103 -0
- package/src/persistence/__tests__/jobs-worker-memory-disabled.test.ts +95 -0
- package/src/persistence/__tests__/slow-query-log.test.ts +266 -0
- package/src/persistence/__tests__/slow-sync-log.test.ts +155 -0
- package/src/persistence/a2a-invite-store.ts +205 -0
- package/src/persistence/attachments-store.ts +94 -10
- package/src/persistence/checkpoints.ts +45 -0
- package/src/persistence/conversation-crud.ts +453 -260
- package/src/persistence/conversation-display-order-migration.ts +8 -2
- package/src/persistence/conversation-group-migration.test.ts +8 -0
- package/src/persistence/conversation-group-migration.ts +9 -0
- package/src/persistence/conversation-queries.ts +244 -125
- package/src/persistence/conversation-row-batch-delete.ts +9 -5
- package/src/persistence/conversation-search-lexical.ts +49 -0
- package/src/persistence/db-async-query.ts +42 -0
- package/src/persistence/db-connection.ts +13 -0
- package/src/persistence/delivery-channels.ts +0 -82
- package/src/persistence/delivery-crud.ts +27 -6
- package/src/persistence/embeddings/__tests__/embedding-identity.test.ts +18 -5
- package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +334 -0
- package/src/persistence/embeddings/embedding-identity.ts +17 -5
- package/src/persistence/embeddings/embedding-runtime-manager.ts +31 -0
- package/src/persistence/embeddings/messages-lexical-index.ts +396 -0
- package/src/persistence/embeddings/qdrant-client.ts +81 -5
- package/src/persistence/group-crud.ts +13 -1
- package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +506 -0
- package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +164 -0
- package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +259 -0
- package/src/persistence/job-handlers/cleanup.ts +57 -9
- package/src/persistence/job-handlers/message-lexical-backfill.ts +187 -0
- package/src/persistence/job-handlers/message-lexical.ts +251 -0
- package/src/persistence/jobs-store.ts +102 -8
- package/src/persistence/jobs-worker.ts +68 -28
- package/src/persistence/llm-request-log-store.ts +35 -19
- package/src/persistence/llm-usage-store.ts +11 -0
- package/src/persistence/memory-lifecycle-hooks.test.ts +65 -0
- package/src/persistence/memory-lifecycle-hooks.ts +55 -1
- package/src/persistence/migrations/028-call-session-mode.ts +1 -1
- package/src/persistence/migrations/116-messages-fts.ts +8 -1
- package/src/persistence/migrations/311-create-subagents-table.ts +50 -0
- package/src/persistence/migrations/312-drop-inbox-conversation-state-table.ts +15 -0
- package/src/persistence/migrations/313-drop-messages-fts.ts +17 -0
- package/src/persistence/migrations/314-add-conversation-enabled-plugins.ts +25 -0
- package/src/persistence/migrations/315-create-a2a-invites.ts +58 -0
- package/src/persistence/migrations/316-drop-contact-channels-invite-id.ts +28 -0
- package/src/persistence/migrations/317-canonical-guardian-requester-signals.ts +24 -0
- package/src/persistence/migrations/318-drop-contact-channel-telemetry.ts +32 -0
- package/src/persistence/migrations/__tests__/315-create-a2a-invites.test.ts +161 -0
- package/src/persistence/migrations/__tests__/316-drop-contact-channels-invite-id.test.ts +156 -0
- package/src/persistence/migrations/__tests__/318-drop-contact-channel-telemetry.test.ts +134 -0
- package/src/persistence/migrations/__tests__/migration-prefix-guard.test.ts +99 -0
- package/src/persistence/raw-query.ts +50 -11
- package/src/persistence/schema/a2a.ts +14 -0
- package/src/persistence/schema/contacts.ts +0 -62
- package/src/persistence/schema/conversation-groups.ts +12 -0
- package/src/persistence/schema/conversations.ts +2 -0
- package/src/persistence/schema/documents.ts +65 -0
- package/src/persistence/schema/guardian.ts +4 -0
- package/src/persistence/schema/index.ts +4 -0
- package/src/persistence/schema/memory-injection.ts +62 -0
- package/src/persistence/schema/workflows.ts +55 -0
- package/src/persistence/slow-query-log.ts +437 -0
- package/src/persistence/slow-sync-log.ts +204 -0
- package/src/persistence/steps.ts +16 -0
- package/src/persistence/subagent-store.ts +125 -0
- package/src/persistence/worker-control.ts +45 -238
- package/src/plugin-api/event-hub-facade.ts +172 -0
- package/src/plugin-api/index.ts +9 -1
- package/src/plugin-api/types.ts +40 -9
- package/src/plugins/defaults/default-plugin-names.ts +60 -0
- package/src/plugins/defaults/index.ts +19 -29
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +46 -67
- package/src/plugins/defaults/memory/__tests__/db-async-query.test.ts +36 -1
- package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +2 -4
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +2 -2
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +9 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +7 -4
- package/src/plugins/defaults/memory/config.ts +11 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -1
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +124 -113
- package/src/plugins/defaults/memory/embeddings.ts +77 -0
- package/src/plugins/defaults/memory/find-most-recent-retrospective-for.ts +66 -0
- package/src/plugins/defaults/memory/frontmatter.ts +50 -0
- package/src/plugins/defaults/memory/graph/bootstrap.test.ts +36 -10
- package/src/plugins/defaults/memory/graph/bootstrap.ts +6 -5
- package/src/plugins/defaults/memory/graph/consolidation.ts +3 -5
- package/src/plugins/defaults/memory/graph/extraction.ts +3 -6
- package/src/plugins/defaults/memory/graph/graph-search.ts +12 -23
- package/src/plugins/defaults/memory/graph/narrative.ts +3 -5
- package/src/plugins/defaults/memory/graph/pattern-scan.ts +3 -5
- package/src/plugins/defaults/memory/graph/retriever.test.ts +1 -6
- package/src/plugins/defaults/memory/graph/retriever.ts +5 -10
- package/src/plugins/defaults/memory/graph/tools.ts +1 -1
- package/src/plugins/defaults/memory/hooks/init.ts +31 -5
- package/src/plugins/defaults/memory/indexer.ts +2 -3
- package/src/plugins/defaults/memory/injectors.ts +2 -2
- package/src/plugins/defaults/memory/job-handler-registration.ts +96 -0
- package/src/plugins/defaults/memory/job-handlers/backfill.ts +4 -26
- package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +6 -19
- package/src/plugins/defaults/memory/job-handlers/embedding.ts +9 -22
- package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +2 -3
- package/src/plugins/defaults/memory/job-handlers.ts +13 -12
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +1 -1
- package/src/plugins/defaults/memory/llm-helpers.ts +76 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +8 -5
- package/src/plugins/defaults/memory/memory-retrospective-startup-cleanup.ts +2 -2
- package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +2 -0
- package/src/plugins/defaults/memory/persistence-hooks.ts +46 -1
- package/src/plugins/defaults/memory/pkb/pkb-index.ts +1 -5
- package/src/plugins/defaults/memory/pkb/pkb-search.ts +2 -2
- package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +4 -6
- package/src/plugins/defaults/memory/routes/memory-item-routes.ts +1 -1
- package/src/plugins/defaults/memory/search/semantic.ts +2 -2
- package/src/plugins/defaults/memory/startup.ts +244 -0
- package/src/plugins/defaults/memory/task-memory-cleanup.ts +8 -0
- package/src/{daemon → plugins/defaults/memory/v2}/__tests__/memory-v2-startup.test.ts +6 -6
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +2 -13
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +4 -5
- package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +1 -8
- package/src/plugins/defaults/memory/v2/activation.ts +2 -4
- package/src/plugins/defaults/memory/v2/cli-command-store.ts +2 -4
- package/src/plugins/defaults/memory/v2/frontmatter-sweep.ts +1 -1
- package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.test.ts +8 -8
- package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.ts +20 -22
- package/src/plugins/defaults/memory/v2/migration.ts +2 -5
- package/src/plugins/defaults/memory/v2/page-store.ts +1 -1
- package/src/plugins/defaults/memory/v2/qdrant.ts +2 -3
- package/src/plugins/defaults/memory/v2/router.ts +5 -5
- package/src/plugins/defaults/memory/v2/sim.ts +2 -4
- package/src/plugins/defaults/memory/v2/skill-store.ts +2 -4
- package/src/plugins/defaults/memory/v2/sweep-job.ts +6 -6
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +11 -9
- package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +107 -2
- package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +69 -0
- package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +181 -0
- package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +7 -3
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +345 -8
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -4
- package/src/plugins/defaults/memory/v3/__tests__/section-needle.test.ts +64 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +18 -13
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +62 -1
- package/src/plugins/defaults/memory/v3/card.ts +1 -4
- package/src/plugins/defaults/memory/v3/dense.ts +60 -25
- package/src/plugins/defaults/memory/v3/edge.ts +1 -1
- package/src/plugins/defaults/memory/v3/gate-flag.ts +20 -0
- package/src/plugins/defaults/memory/v3/gate.ts +158 -0
- package/src/plugins/defaults/memory/v3/maintain-job.ts +2 -4
- package/src/plugins/defaults/memory/v3/orchestrate.ts +186 -25
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +7 -9
- package/src/plugins/defaults/memory/v3/pool-select.ts +6 -6
- package/src/plugins/defaults/memory/v3/prune.ts +2 -2
- package/src/plugins/defaults/memory/v3/section-dense-store.ts +8 -11
- package/src/plugins/defaults/memory/v3/section-needle.ts +27 -7
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +10 -0
- package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +1 -4
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +5 -0
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +12 -0
- package/src/plugins/injector-registry.ts +13 -2
- package/src/plugins/mtime-cache.ts +36 -47
- package/src/plugins/pipeline.ts +15 -1
- package/src/plugins/types.ts +14 -23
- package/src/prompts/__tests__/system-prompt.test.ts +170 -32
- package/src/prompts/persona-resolver.ts +13 -9
- package/src/prompts/system-prompt.ts +30 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -119
- package/src/prompts/templates/SOUL.md +43 -13
- package/src/prompts/templates/system-sections.ts +32 -27
- package/src/prompts/templates/users/default.md +19 -0
- package/src/providers/__tests__/placeholder-sentinels.test.ts +56 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +96 -1
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +14 -1
- package/src/providers/openai/responses-provider.ts +18 -0
- package/src/providers/placeholder-sentinels.ts +18 -8
- package/src/providers/retry.ts +1 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +0 -64
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +92 -79
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +98 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +62 -3
- package/src/providers/speech-to-text/provider-catalog.ts +0 -89
- package/src/providers/speech-to-text/resolve.ts +55 -0
- package/src/providers/tool-progress-events.test.ts +50 -0
- package/src/providers/tool-progress-events.ts +99 -0
- package/src/providers/types.ts +1 -1
- package/src/providers/vellum-model-routing.test.ts +51 -0
- package/src/providers/vellum-model-routing.ts +84 -0
- package/src/runtime/AGENTS.md +5 -4
- package/src/runtime/__tests__/agent-wake.test.ts +158 -1
- package/src/runtime/__tests__/channel-verification-service.test.ts +2 -2
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +37 -8
- package/src/runtime/access-request-helper.ts +154 -3
- package/src/runtime/actor-trust-resolver.ts +7 -21
- package/src/runtime/agent-wake.ts +41 -1
- package/src/runtime/assistant-event.ts +86 -12
- package/src/runtime/auth/__tests__/route-policy.test.ts +31 -0
- package/src/runtime/channel-approval-types.ts +52 -2
- package/src/runtime/channel-approvals.ts +23 -8
- package/src/runtime/channel-invite-transport.ts +2 -2
- package/src/runtime/channel-invite-transports/voice.ts +1 -1
- package/src/runtime/channel-readiness-service.ts +3 -5
- package/src/runtime/channel-retry-sweep.ts +18 -24
- package/src/runtime/channel-verification-service.ts +9 -11
- package/src/runtime/finalize-event-delivery.ts +30 -16
- package/src/runtime/guardian-action-service.ts +10 -11
- package/src/runtime/guardian-reply-router.ts +122 -21
- package/src/runtime/http-server.ts +44 -101
- package/src/runtime/introduction-policy.test.ts +214 -0
- package/src/runtime/introduction-policy.ts +284 -0
- package/src/runtime/invite-service.ts +104 -361
- package/src/runtime/local-actor-identity.test.ts +38 -0
- package/src/runtime/local-actor-identity.ts +14 -0
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +22 -0
- package/src/runtime/middleware/rate-limiter.ts +30 -0
- package/src/runtime/middleware/twilio-validation.ts +5 -7
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +28 -0
- package/src/runtime/migrations/origin-mode.ts +6 -6
- package/src/runtime/pre-first-message-gate.ts +1 -0
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +6 -35
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +24 -3
- package/src/runtime/routes/__tests__/contact-routes.test.ts +80 -15
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +7 -1
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -3
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +142 -1
- package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +119 -30
- package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +70 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +194 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +755 -29
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +4 -0
- package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +70 -0
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +161 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -1
- package/src/runtime/routes/acp-routes.ts +15 -3
- package/src/runtime/routes/app-management-routes.ts +4 -7
- package/src/runtime/routes/approval-routes.ts +4 -0
- package/src/runtime/routes/attachment-routes.ts +54 -3
- package/src/runtime/routes/avatar-routes.ts +0 -4
- package/src/runtime/routes/background-tool-routes.ts +34 -5
- package/src/runtime/routes/btw-routes.ts +32 -4
- package/src/runtime/routes/channel-route-shared.ts +18 -14
- package/src/runtime/routes/channel-verification-routes.ts +0 -3
- package/src/runtime/routes/contact-routes.ts +298 -117
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +74 -37
- package/src/runtime/routes/conversation-query-routes.ts +5 -2
- package/src/runtime/routes/conversation-routes.ts +222 -82
- package/src/runtime/routes/conversations-import-routes.ts +8 -1
- package/src/runtime/routes/credential-prompt-routes.ts +20 -5
- package/src/runtime/routes/debug-routes.ts +1 -0
- package/src/runtime/routes/documents-routes.ts +5 -2
- package/src/runtime/routes/empty-state-greeting-cache.ts +37 -10
- package/src/runtime/routes/global-search-routes.ts +2 -1
- package/src/runtime/routes/guardian-approval-interception.ts +51 -0
- package/src/runtime/routes/home-feed-routes.ts +60 -1
- package/src/runtime/routes/identity-routes.ts +4 -87
- package/src/runtime/routes/inbound-message-handler.ts +82 -41
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +430 -57
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +302 -572
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +76 -270
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +21 -116
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +9 -1
- package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +158 -0
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +22 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +22 -16
- package/src/runtime/routes/internal-twilio-routes.ts +0 -15
- package/src/runtime/routes/messages-lexical-routes.ts +75 -0
- package/src/runtime/routes/migration-routes.ts +69 -6
- package/src/runtime/routes/monitoring-routes.ts +237 -0
- package/src/runtime/routes/plugins-routes.ts +329 -8
- package/src/runtime/routes/schedule-routes.ts +49 -8
- package/src/runtime/routes/schedule-worker-routes.ts +210 -0
- package/src/runtime/routes/secret-routes.ts +1 -1
- package/src/runtime/routes/surface-conversation-resolver.ts +1 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +54 -9
- package/src/runtime/services/__tests__/auto-analysis-enqueue.test.ts +30 -6
- package/src/runtime/services/__tests__/conversation-analyze-job.test.ts +43 -8
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +20 -0
- package/src/runtime/services/analyze-conversation.ts +14 -4
- package/src/runtime/services/conversation-serializer.ts +5 -0
- package/src/runtime/slack-no-response.ts +47 -0
- package/src/runtime/slack-reply-session.test.ts +633 -0
- package/src/runtime/slack-reply-session.ts +382 -0
- package/src/runtime/slack-task-progress.ts +95 -0
- package/src/runtime/sync/resource-sync-events.ts +25 -0
- package/src/runtime/tool-grant-request-helper.ts +17 -1
- package/src/runtime/trust-class.test.ts +141 -0
- package/src/runtime/trust-class.ts +121 -0
- package/src/runtime/trust-verdict-consumer.ts +7 -6
- package/src/schedule/__tests__/run-script.test.ts +21 -0
- package/src/schedule/__tests__/worker-control.test.ts +241 -0
- package/src/schedule/run-script.ts +13 -6
- package/src/schedule/schedule-usage-store.ts +1 -0
- package/src/schedule/scheduler.ts +178 -127
- package/src/schedule/worker-control.ts +118 -0
- package/src/schedule/worker.ts +131 -0
- package/src/skills/__tests__/categories-cache.test.ts +38 -0
- package/src/skills/categories-cache.ts +40 -5
- package/src/skills/managed-store.ts +41 -6
- package/src/skills/validate-input.ts +47 -0
- package/src/stt/types.ts +5 -9
- package/src/subagent/manager.ts +267 -39
- package/src/subagent/types.ts +6 -2
- package/src/subagent/validate-allowlists.ts +65 -0
- package/src/telemetry/watchdog-direct-emit.test.ts +109 -0
- package/src/telemetry/watchdog-direct-emit.ts +89 -0
- package/src/tools/acp/list-agents.test.ts +0 -6
- package/src/tools/background-tool-registry.ts +61 -0
- package/src/tools/document/document-comment-tool.test.ts +4 -0
- package/src/tools/host-terminal/host-shell.test.ts +397 -0
- package/src/tools/host-terminal/host-shell.ts +203 -14
- package/src/tools/skills/find-similar-skills.test.ts +152 -1
- package/src/tools/skills/find-similar-skills.ts +30 -4
- package/src/tools/skills/load.ts +36 -0
- package/src/tools/skills/scaffold-managed.ts +66 -34
- package/src/tools/skills/skill-script-runner.ts +17 -0
- package/src/tools/skills/skill-tool-factory.ts +5 -1
- package/src/tools/terminal/safe-env.ts +3 -0
- package/src/tools/terminal/shell.test.ts +177 -0
- package/src/tools/terminal/shell.ts +98 -4
- package/src/tools/tool-approval-handler.ts +106 -29
- package/{node_modules/@vellumai/skill-host-contracts/src → src/tools}/tool-types.ts +16 -17
- package/src/tools/types.ts +29 -17
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/tts/__tests__/provider-adapters.test.ts +37 -0
- package/src/tts/__tests__/provider-catalog.test.ts +21 -0
- package/src/tts/provider-catalog.ts +40 -9
- package/src/tts/providers/fish-audio-provider.ts +19 -10
- package/src/tts/types.ts +3 -4
- package/src/util/__tests__/process-tree.test.ts +10 -2
- package/src/util/__tests__/text-spacing.test.ts +53 -0
- package/src/util/cgroup-memory.ts +142 -0
- package/src/util/image-conversion.ts +281 -0
- package/src/util/phone.ts +1 -8
- package/src/util/platform.ts +33 -0
- package/src/util/process-tree.ts +10 -2
- package/src/util/text-spacing.ts +53 -0
- package/src/util/worker-process.ts +271 -0
- package/src/workflows/journal-store.ts +16 -1
- package/src/workflows/library.test.ts +35 -20
- package/src/workflows/library.ts +105 -66
- package/src/workspace/git-service.ts +1 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +83 -31
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +41 -22
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +1 -1
- package/src/workspace/migrations/118-seed-now-md.ts +64 -0
- package/src/workspace/migrations/119-strip-persisted-memory-v3-tuning-defaults.ts +169 -0
- package/src/workspace/migrations/120-revise-onboarding-threads.ts +47 -0
- package/src/workspace/migrations/121-seed-default-user-guardrails.ts +82 -0
- package/src/workspace/migrations/122-relocate-default-user-boundary.ts +111 -0
- package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +85 -0
- package/src/workspace/migrations/__tests__/119-strip-persisted-memory-v3-tuning-defaults.test.ts +181 -0
- package/src/workspace/migrations/registry.ts +12 -0
- package/drizzle/0000_dizzy_maggott.sql +0 -301
- package/drizzle/meta/0000_snapshot.json +0 -1933
- package/drizzle/meta/_journal.json +0 -13
- package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +0 -887
- package/node_modules/@vellumai/skill-host-contracts/bun.lock +0 -24
- package/node_modules/@vellumai/skill-host-contracts/package.json +0 -18
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -97
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +0 -1349
- package/node_modules/@vellumai/skill-host-contracts/src/index.ts +0 -6
- package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +0 -11
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +0 -32
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +0 -333
- package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +0 -20
- package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +0 -12
- package/src/__tests__/invite-redemption-service.test.ts +0 -1180
- package/src/__tests__/invite-service-ipc.test.ts +0 -350
- package/src/__tests__/job-handler-registry-guard.test.ts +0 -125
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -203
- package/src/__tests__/relay-server.test.ts +0 -6153
- package/src/__tests__/telephony-stt-routing.test.ts +0 -329
- package/src/__tests__/voice-invite-redemption.test.ts +0 -702
- package/src/calls/relay-server.ts +0 -2463
- package/src/calls/telephony-stt-routing.ts +0 -145
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -128
- package/src/daemon/__tests__/daemon-skill-host.test.ts +0 -273
- package/src/daemon/daemon-skill-host.ts +0 -262
- package/src/daemon/message-types/meet.ts +0 -143
- package/src/daemon/shutdown-registry.ts +0 -40
- package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -253
- package/src/ipc/__tests__/skill-server.test.ts +0 -182
- package/src/ipc/skill-ipc-types.ts +0 -54
- package/src/ipc/skill-routes/__tests__/config.test.ts +0 -146
- package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +0 -448
- package/src/ipc/skill-routes/__tests__/identity.test.ts +0 -62
- package/src/ipc/skill-routes/__tests__/log.test.ts +0 -133
- package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -203
- package/src/ipc/skill-routes/__tests__/platform.test.ts +0 -111
- package/src/ipc/skill-routes/__tests__/providers.test.ts +0 -265
- package/src/ipc/skill-routes/__tests__/registries.test.ts +0 -400
- package/src/ipc/skill-routes/config.ts +0 -47
- package/src/ipc/skill-routes/events.ts +0 -147
- package/src/ipc/skill-routes/identity.ts +0 -21
- package/src/ipc/skill-routes/index.ts +0 -37
- package/src/ipc/skill-routes/log.ts +0 -40
- package/src/ipc/skill-routes/memory.ts +0 -92
- package/src/ipc/skill-routes/platform.ts +0 -39
- package/src/ipc/skill-routes/providers.ts +0 -162
- package/src/ipc/skill-routes/registries.ts +0 -338
- package/src/ipc/skill-server.ts +0 -831
- package/src/ipc/skill-socket-path.ts +0 -20
- package/src/jobs/register-job-handlers.ts +0 -74
- package/src/persistence/invite-store.ts +0 -481
- package/src/plugins/job-handler-registry.ts +0 -94
- package/src/providers/cache-control.ts +0 -26
- package/src/runtime/__tests__/runtime-mode.test.ts +0 -62
- package/src/runtime/invite-redemption-service.ts +0 -828
- package/src/runtime/invite-redemption-templates.ts +0 -46
- package/src/runtime/runtime-mode.ts +0 -40
- package/src/runtime/slack-dm-text-delivery.ts +0 -177
- package/src/util/voice-code.ts +0 -31
|
@@ -191,7 +191,6 @@ mock.module("../calls/voice-session-bridge.js", () => {
|
|
|
191
191
|
mockStartVoiceTurn = mock(createMockVoiceTurn(["Hello", " there"]));
|
|
192
192
|
return {
|
|
193
193
|
startVoiceTurn: (...args: unknown[]) => mockStartVoiceTurn(...args),
|
|
194
|
-
setVoiceBridgeDeps: () => {},
|
|
195
194
|
};
|
|
196
195
|
});
|
|
197
196
|
|
|
@@ -291,7 +290,6 @@ interface MockTransport extends CallTransport {
|
|
|
291
290
|
sentPlayUrls: string[];
|
|
292
291
|
endCalled: boolean;
|
|
293
292
|
endReason: string | undefined;
|
|
294
|
-
mockConnectionState: string;
|
|
295
293
|
}
|
|
296
294
|
|
|
297
295
|
function createMockTransport(): MockTransport {
|
|
@@ -300,7 +298,6 @@ function createMockTransport(): MockTransport {
|
|
|
300
298
|
sentPlayUrls: [] as string[],
|
|
301
299
|
_endCalled: false,
|
|
302
300
|
_endReason: undefined as string | undefined,
|
|
303
|
-
_connectionState: "connected",
|
|
304
301
|
};
|
|
305
302
|
|
|
306
303
|
return {
|
|
@@ -316,12 +313,6 @@ function createMockTransport(): MockTransport {
|
|
|
316
313
|
get endReason() {
|
|
317
314
|
return state._endReason;
|
|
318
315
|
},
|
|
319
|
-
get mockConnectionState() {
|
|
320
|
-
return state._connectionState;
|
|
321
|
-
},
|
|
322
|
-
set mockConnectionState(v: string) {
|
|
323
|
-
state._connectionState = v;
|
|
324
|
-
},
|
|
325
316
|
sendTextToken(token: string, last: boolean) {
|
|
326
317
|
state.sentTokens.push({ token, last });
|
|
327
318
|
},
|
|
@@ -332,9 +323,6 @@ function createMockTransport(): MockTransport {
|
|
|
332
323
|
state._endCalled = true;
|
|
333
324
|
state._endReason = reason;
|
|
334
325
|
},
|
|
335
|
-
getConnectionState() {
|
|
336
|
-
return state._connectionState;
|
|
337
|
-
},
|
|
338
326
|
} as MockTransport;
|
|
339
327
|
}
|
|
340
328
|
|
|
@@ -1025,6 +1013,52 @@ describe("call-controller", () => {
|
|
|
1025
1013
|
controller.destroy();
|
|
1026
1014
|
});
|
|
1027
1015
|
|
|
1016
|
+
test("lock-contention rejection: speaks a brief re-prompt, no technical-issue speech, returns to idle", async () => {
|
|
1017
|
+
mockStartVoiceTurn.mockImplementation(async () => {
|
|
1018
|
+
throw new Error("Conversation is already processing a message");
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
const { relay, controller } = setupController();
|
|
1022
|
+
|
|
1023
|
+
await controller.handleCallerUtterance("Hello");
|
|
1024
|
+
|
|
1025
|
+
// A wedged prior turn must never surface a technical-error message.
|
|
1026
|
+
const errorTokens = relay.sentTokens.filter((t) =>
|
|
1027
|
+
t.token.includes("technical issue"),
|
|
1028
|
+
);
|
|
1029
|
+
expect(errorTokens.length).toBe(0);
|
|
1030
|
+
|
|
1031
|
+
// A brief natural re-prompt (last=true) is spoken so the caller knows to
|
|
1032
|
+
// repeat themselves and the relay transitions back to listening.
|
|
1033
|
+
const reprompts = relay.sentTokens.filter(
|
|
1034
|
+
(t) => t.token === "Sorry, could you say that again?" && t.last === true,
|
|
1035
|
+
);
|
|
1036
|
+
expect(reprompts.length).toBeGreaterThan(0);
|
|
1037
|
+
|
|
1038
|
+
// Controller goes idle and re-arms the silence watchdog.
|
|
1039
|
+
expect(controller.getState()).toBe("idle");
|
|
1040
|
+
|
|
1041
|
+
controller.destroy();
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
test("genuine non-lock-contention error still speaks the technical-issue fallback", async () => {
|
|
1045
|
+
mockStartVoiceTurn.mockImplementation(async () => {
|
|
1046
|
+
throw new Error("boom");
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
const { relay, controller } = setupController();
|
|
1050
|
+
|
|
1051
|
+
await controller.handleCallerUtterance("Hello");
|
|
1052
|
+
|
|
1053
|
+
const errorTokens = relay.sentTokens.filter((t) =>
|
|
1054
|
+
t.token.includes("technical issue"),
|
|
1055
|
+
);
|
|
1056
|
+
expect(errorTokens.length).toBeGreaterThan(0);
|
|
1057
|
+
expect(controller.getState()).toBe("idle");
|
|
1058
|
+
|
|
1059
|
+
controller.destroy();
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1028
1062
|
test("handleUserAnswer: returns false when no pending consultation exists", async () => {
|
|
1029
1063
|
const { controller } = setupController();
|
|
1030
1064
|
|
|
@@ -1054,17 +1088,13 @@ describe("call-controller", () => {
|
|
|
1054
1088
|
onComplete: () => void;
|
|
1055
1089
|
}) => {
|
|
1056
1090
|
return new Promise((resolve) => {
|
|
1057
|
-
//
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
opts.onComplete();
|
|
1061
|
-
resolve({ turnId: "run-1", abort: () => {} });
|
|
1062
|
-
}, 1000);
|
|
1091
|
+
// Emit a token right away so the assistant is actively speaking
|
|
1092
|
+
// (state flips to `speaking`) before the interrupt arrives.
|
|
1093
|
+
opts.onTextDelta("This should be interrupted");
|
|
1063
1094
|
|
|
1064
1095
|
opts.signal?.addEventListener(
|
|
1065
1096
|
"abort",
|
|
1066
1097
|
() => {
|
|
1067
|
-
clearTimeout(timeout);
|
|
1068
1098
|
// In the real system, generation_cancelled triggers
|
|
1069
1099
|
// onComplete via the event sink. The AbortSignal listener
|
|
1070
1100
|
// in call-controller also resolves turnComplete defensively.
|
|
@@ -2123,7 +2153,10 @@ describe("call-controller", () => {
|
|
|
2123
2153
|
"Are you still there?",
|
|
2124
2154
|
);
|
|
2125
2155
|
await new Promise((r) => setTimeout(r, 10));
|
|
2126
|
-
|
|
2156
|
+
// The turn is paused before emitting any token, so the controller is still
|
|
2157
|
+
// in the pre-speech `processing` phase (it only flips to `speaking` once
|
|
2158
|
+
// real outbound audio/tokens begin).
|
|
2159
|
+
expect(controller.getState()).toBe("processing");
|
|
2127
2160
|
|
|
2128
2161
|
// Answer arrives while the controller is processing/speaking
|
|
2129
2162
|
const accepted = await controller.handleUserAnswer("3pm works");
|
|
@@ -2400,30 +2433,11 @@ describe("call-controller", () => {
|
|
|
2400
2433
|
|
|
2401
2434
|
// ── Silence suppression during guardian wait ──────────────────────
|
|
2402
2435
|
|
|
2403
|
-
test('silence timeout suppressed during guardian wait: does not say "Are you still there?"', async () => {
|
|
2404
|
-
mockSilenceTimeoutMs = 20; // Short timeout for testing
|
|
2405
|
-
const { relay, controller } = setupController();
|
|
2406
|
-
|
|
2407
|
-
// Simulate guardian wait state on the relay
|
|
2408
|
-
relay.mockConnectionState = "awaiting_guardian_decision";
|
|
2409
|
-
|
|
2410
|
-
// Wait for the silence timeout to fire
|
|
2411
|
-
await new Promise((r) => setTimeout(r, 30));
|
|
2412
|
-
|
|
2413
|
-
// "Are you still there?" should NOT have been sent
|
|
2414
|
-
const silenceTokens = relay.sentTokens.filter((t) =>
|
|
2415
|
-
t.token.includes("Are you still there?"),
|
|
2416
|
-
);
|
|
2417
|
-
expect(silenceTokens.length).toBe(0);
|
|
2418
|
-
|
|
2419
|
-
controller.destroy();
|
|
2420
|
-
});
|
|
2421
|
-
|
|
2422
2436
|
test("silence timeout fires normally when not in guardian wait", async () => {
|
|
2423
2437
|
mockSilenceTimeoutMs = 20; // Short timeout for testing
|
|
2424
2438
|
const { relay, controller } = setupController();
|
|
2425
2439
|
|
|
2426
|
-
//
|
|
2440
|
+
// No pending guardian consultation — the nudge should fire.
|
|
2427
2441
|
|
|
2428
2442
|
// Wait for the silence timeout to fire
|
|
2429
2443
|
await new Promise((r) => setTimeout(r, 30));
|
|
@@ -2456,8 +2470,6 @@ describe("call-controller", () => {
|
|
|
2456
2470
|
|
|
2457
2471
|
// Verify a guardian input request is now pending
|
|
2458
2472
|
expect(controller.getPendingConsultationQuestionId()).not.toBeNull();
|
|
2459
|
-
// Relay state is still 'connected' (not 'awaiting_guardian_decision')
|
|
2460
|
-
expect(relay.mockConnectionState).toBe("connected");
|
|
2461
2473
|
|
|
2462
2474
|
// Clear any tokens from the turn itself
|
|
2463
2475
|
relay.sentTokens.length = 0;
|
|
@@ -2682,6 +2694,65 @@ describe("call-controller", () => {
|
|
|
2682
2694
|
controller.destroy();
|
|
2683
2695
|
});
|
|
2684
2696
|
|
|
2697
|
+
test("synthesized provider: a superseded run does not emit native fallback text", async () => {
|
|
2698
|
+
const cfg = loadConfig();
|
|
2699
|
+
cfg.services.tts.provider = "fish-audio";
|
|
2700
|
+
cfg.services.tts.providers["fish-audio"].referenceId = "fish-ref-123";
|
|
2701
|
+
|
|
2702
|
+
_resetTtsProviderRegistry();
|
|
2703
|
+
// elevenlabs present so native fallback is available for fish-audio.
|
|
2704
|
+
const elevenlabs: TtsProvider = {
|
|
2705
|
+
id: "elevenlabs",
|
|
2706
|
+
capabilities: { supportsStreaming: false, supportedFormats: ["mp3"] },
|
|
2707
|
+
async synthesize() {
|
|
2708
|
+
return { audio: Buffer.from(""), contentType: "audio/mpeg" };
|
|
2709
|
+
},
|
|
2710
|
+
};
|
|
2711
|
+
registerTtsProvider(elevenlabs);
|
|
2712
|
+
|
|
2713
|
+
let releaseSynth: (() => void) | undefined;
|
|
2714
|
+
const synthGate = new Promise<void>((resolve) => {
|
|
2715
|
+
releaseSynth = resolve;
|
|
2716
|
+
});
|
|
2717
|
+
const fishAudioGatedFailing: TtsProvider = {
|
|
2718
|
+
id: "fish-audio",
|
|
2719
|
+
capabilities: {
|
|
2720
|
+
supportsStreaming: true,
|
|
2721
|
+
supportedFormats: ["mp3", "wav", "opus"],
|
|
2722
|
+
},
|
|
2723
|
+
async synthesize() {
|
|
2724
|
+
throw new Error("fish-audio synth failure");
|
|
2725
|
+
},
|
|
2726
|
+
async synthesizeStream() {
|
|
2727
|
+
// Ignore the abort signal, block, then fail with no audio — this would
|
|
2728
|
+
// normally trigger the native fallback text emission.
|
|
2729
|
+
await synthGate;
|
|
2730
|
+
throw new Error("fish-audio stream failure");
|
|
2731
|
+
},
|
|
2732
|
+
};
|
|
2733
|
+
registerTtsProvider(fishAudioGatedFailing);
|
|
2734
|
+
|
|
2735
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2736
|
+
createMockVoiceTurn(["Stale synthesized response"]),
|
|
2737
|
+
);
|
|
2738
|
+
const { relay, controller } = setupController();
|
|
2739
|
+
|
|
2740
|
+
const turn = controller.handleCallerUtterance("Hi");
|
|
2741
|
+
await new Promise((r) => setTimeout(r, 20)); // reach synthesis, blocked
|
|
2742
|
+
|
|
2743
|
+
// Supersede the turn mid-synthesis, then let synthesis fail.
|
|
2744
|
+
controller.handleInterrupt();
|
|
2745
|
+
releaseSynth?.();
|
|
2746
|
+
await turn;
|
|
2747
|
+
|
|
2748
|
+
// The stale run must NOT leak its native fallback text into the relay.
|
|
2749
|
+
const emitted = relay.sentTokens.map((t) => t.token).join("");
|
|
2750
|
+
expect(emitted).not.toContain("Stale synthesized response");
|
|
2751
|
+
expect(relay.sentPlayUrls.length).toBe(0);
|
|
2752
|
+
|
|
2753
|
+
controller.destroy();
|
|
2754
|
+
});
|
|
2755
|
+
|
|
2685
2756
|
test("synthesized provider: play URL uses public base URL", async () => {
|
|
2686
2757
|
const cfg = loadConfig();
|
|
2687
2758
|
cfg.ingress.publicBaseUrl = "https://twilio.example.com/";
|
|
@@ -2726,11 +2797,118 @@ describe("call-controller", () => {
|
|
|
2726
2797
|
controller.destroy();
|
|
2727
2798
|
});
|
|
2728
2799
|
|
|
2729
|
-
test("
|
|
2800
|
+
test("synthesized provider: stays 'processing' during synthesis latency, so barge-in cannot abort an inaudible turn", async () => {
|
|
2801
|
+
const cfg = loadConfig();
|
|
2802
|
+
cfg.services.tts.provider = "fish-audio";
|
|
2803
|
+
cfg.services.tts.providers["fish-audio"].referenceId = "fish-ref-123";
|
|
2804
|
+
|
|
2805
|
+
_resetTtsProviderRegistry();
|
|
2806
|
+
let releaseChunk: (() => void) | undefined;
|
|
2807
|
+
const chunkGate = new Promise<void>((resolve) => {
|
|
2808
|
+
releaseChunk = resolve;
|
|
2809
|
+
});
|
|
2810
|
+
const fishAudioStreaming: TtsProvider = {
|
|
2811
|
+
id: "fish-audio",
|
|
2812
|
+
capabilities: {
|
|
2813
|
+
supportsStreaming: true,
|
|
2814
|
+
supportedFormats: ["mp3", "wav", "opus"],
|
|
2815
|
+
},
|
|
2816
|
+
async synthesize() {
|
|
2817
|
+
return { audio: Buffer.from("x"), contentType: "audio/mpeg" };
|
|
2818
|
+
},
|
|
2819
|
+
async synthesizeStream(_request, onChunk) {
|
|
2820
|
+
// Simulate provider latency: emit no audio until the gate releases.
|
|
2821
|
+
await chunkGate;
|
|
2822
|
+
onChunk(Buffer.from("fish-audio-stream"));
|
|
2823
|
+
return {
|
|
2824
|
+
audio: Buffer.from("fish-audio-stream"),
|
|
2825
|
+
contentType: "audio/mpeg",
|
|
2826
|
+
};
|
|
2827
|
+
},
|
|
2828
|
+
};
|
|
2829
|
+
registerTtsProvider(fishAudioStreaming);
|
|
2830
|
+
|
|
2831
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2832
|
+
createMockVoiceTurn(["Hello from synthesized path."]),
|
|
2833
|
+
);
|
|
2834
|
+
const { relay, controller } = setupController();
|
|
2835
|
+
|
|
2836
|
+
const turn = controller.handleCallerUtterance("Hi");
|
|
2837
|
+
// Let the turn generate its text and reach synthesis, blocked on the gate.
|
|
2838
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
2839
|
+
|
|
2840
|
+
// No audio has reached the caller yet (play URL not sent), so the controller
|
|
2841
|
+
// must stay in `processing` — a barge-in here would abort a turn the caller
|
|
2842
|
+
// cannot hear. See JARVIS-1232.
|
|
2843
|
+
expect(relay.sentPlayUrls.length).toBe(0);
|
|
2844
|
+
expect(controller.getState()).toBe("processing");
|
|
2845
|
+
expect(controller.handleBargeIn()).toBe(false);
|
|
2846
|
+
|
|
2847
|
+
// Release audio → play URL sent → turn finishes and returns to idle.
|
|
2848
|
+
releaseChunk?.();
|
|
2849
|
+
await turn;
|
|
2850
|
+
expect(relay.sentPlayUrls.length).toBeGreaterThan(0);
|
|
2851
|
+
expect(controller.getState()).toBe("idle");
|
|
2852
|
+
|
|
2853
|
+
controller.destroy();
|
|
2854
|
+
});
|
|
2855
|
+
|
|
2856
|
+
test("synthesized provider: a superseded turn does not send a stale play URL", async () => {
|
|
2857
|
+
const cfg = loadConfig();
|
|
2858
|
+
cfg.services.tts.provider = "fish-audio";
|
|
2859
|
+
cfg.services.tts.providers["fish-audio"].referenceId = "fish-ref-123";
|
|
2860
|
+
|
|
2861
|
+
_resetTtsProviderRegistry();
|
|
2862
|
+
let releaseChunk: (() => void) | undefined;
|
|
2863
|
+
const chunkGate = new Promise<void>((resolve) => {
|
|
2864
|
+
releaseChunk = resolve;
|
|
2865
|
+
});
|
|
2866
|
+
const fishAudioStreaming: TtsProvider = {
|
|
2867
|
+
id: "fish-audio",
|
|
2868
|
+
capabilities: {
|
|
2869
|
+
supportsStreaming: true,
|
|
2870
|
+
supportedFormats: ["mp3", "wav", "opus"],
|
|
2871
|
+
},
|
|
2872
|
+
async synthesize() {
|
|
2873
|
+
return { audio: Buffer.from("x"), contentType: "audio/mpeg" };
|
|
2874
|
+
},
|
|
2875
|
+
async synthesizeStream(_request, onChunk) {
|
|
2876
|
+
// Block until released so we can supersede the turn mid-synthesis.
|
|
2877
|
+
await chunkGate;
|
|
2878
|
+
onChunk(Buffer.from("stale-audio"));
|
|
2879
|
+
return { audio: Buffer.from("stale-audio"), contentType: "audio/mpeg" };
|
|
2880
|
+
},
|
|
2881
|
+
};
|
|
2882
|
+
registerTtsProvider(fishAudioStreaming);
|
|
2883
|
+
|
|
2884
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2885
|
+
createMockVoiceTurn(["Hello from synthesized path."]),
|
|
2886
|
+
);
|
|
2887
|
+
const { relay, controller } = setupController();
|
|
2888
|
+
|
|
2889
|
+
const turn = controller.handleCallerUtterance("first");
|
|
2890
|
+
// Let the turn generate text and block inside provider synthesis.
|
|
2891
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
2892
|
+
expect(relay.sentPlayUrls.length).toBe(0);
|
|
2893
|
+
|
|
2894
|
+
// Supersede the in-flight synthesized turn (bumps run version + aborts synthesis).
|
|
2895
|
+
controller.handleInterrupt();
|
|
2896
|
+
|
|
2897
|
+
// Releasing the stale synthesis must NOT send a play URL for the old run,
|
|
2898
|
+
// nor inject a stale end-of-turn marker into the relay stream.
|
|
2899
|
+
releaseChunk?.();
|
|
2900
|
+
await turn;
|
|
2901
|
+
expect(relay.sentPlayUrls.length).toBe(0);
|
|
2902
|
+
expect(relay.sentTokens.filter((t) => t.last).length).toBe(0);
|
|
2903
|
+
|
|
2904
|
+
controller.destroy();
|
|
2905
|
+
});
|
|
2906
|
+
|
|
2907
|
+
test("Deepgram selected path resolves useSynthesizedPath to true", async () => {
|
|
2730
2908
|
const cfg = loadConfig();
|
|
2731
2909
|
cfg.services.tts.provider = "deepgram";
|
|
2732
2910
|
|
|
2733
|
-
const result = resolveCallTtsProvider();
|
|
2911
|
+
const result = await resolveCallTtsProvider();
|
|
2734
2912
|
expect(result.provider).not.toBeNull();
|
|
2735
2913
|
expect(result.provider!.id).toBe("deepgram");
|
|
2736
2914
|
expect(result.useSynthesizedPath).toBe(true);
|
|
@@ -2841,16 +3019,13 @@ describe("call-controller", () => {
|
|
|
2841
3019
|
onComplete: () => void;
|
|
2842
3020
|
}) => {
|
|
2843
3021
|
return new Promise((resolve) => {
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
resolve({ turnId: "run-1", abort: () => {} });
|
|
2848
|
-
}, 1000);
|
|
3022
|
+
// Emit a token right away so the assistant is actively speaking
|
|
3023
|
+
// before the interrupt arrives.
|
|
3024
|
+
opts.onTextDelta("This should be interrupted");
|
|
2849
3025
|
|
|
2850
3026
|
opts.signal?.addEventListener(
|
|
2851
3027
|
"abort",
|
|
2852
3028
|
() => {
|
|
2853
|
-
clearTimeout(timeout);
|
|
2854
3029
|
opts.onComplete();
|
|
2855
3030
|
resolve({ turnId: "run-1", abort: () => {} });
|
|
2856
3031
|
},
|
|
@@ -2879,30 +3054,30 @@ describe("call-controller", () => {
|
|
|
2879
3054
|
// ── Shared TTS provider resolution ──────────────────────────────────
|
|
2880
3055
|
|
|
2881
3056
|
describe("resolveCallTtsProvider (shared helper)", () => {
|
|
2882
|
-
test("returns native path with elevenlabs (non-streaming provider)", () => {
|
|
3057
|
+
test("returns native path with elevenlabs (non-streaming provider)", async () => {
|
|
2883
3058
|
// Default config has provider: "elevenlabs" which is registered as
|
|
2884
3059
|
// non-streaming in registerTestTtsProviders()
|
|
2885
|
-
const result = resolveCallTtsProvider();
|
|
3060
|
+
const result = await resolveCallTtsProvider();
|
|
2886
3061
|
expect(result.provider).not.toBeNull();
|
|
2887
3062
|
expect(result.provider!.id).toBe("elevenlabs");
|
|
2888
3063
|
expect(result.useSynthesizedPath).toBe(false);
|
|
2889
3064
|
expect(result.audioFormat).toBe("mp3");
|
|
2890
3065
|
});
|
|
2891
3066
|
|
|
2892
|
-
test("returns fallback when provider registry is empty", () => {
|
|
3067
|
+
test("returns fallback when provider registry is empty", async () => {
|
|
2893
3068
|
_resetTtsProviderRegistry();
|
|
2894
|
-
const result = resolveCallTtsProvider();
|
|
3069
|
+
const result = await resolveCallTtsProvider();
|
|
2895
3070
|
expect(result.provider).toBeNull();
|
|
2896
3071
|
expect(result.useSynthesizedPath).toBe(false);
|
|
2897
3072
|
expect(result.audioFormat).toBe("mp3");
|
|
2898
3073
|
});
|
|
2899
3074
|
|
|
2900
|
-
test("degrades fish-audio synthesized path when referenceId is missing", () => {
|
|
3075
|
+
test("degrades fish-audio synthesized path when referenceId is missing", async () => {
|
|
2901
3076
|
const cfg = loadConfig();
|
|
2902
3077
|
cfg.services.tts.provider = "fish-audio";
|
|
2903
3078
|
cfg.services.tts.providers["fish-audio"].referenceId = "";
|
|
2904
3079
|
|
|
2905
|
-
const result = resolveCallTtsProvider();
|
|
3080
|
+
const result = await resolveCallTtsProvider();
|
|
2906
3081
|
expect(result.provider).toBeNull();
|
|
2907
3082
|
expect(result.useSynthesizedPath).toBe(false);
|
|
2908
3083
|
expect(result.audioFormat).toBe("mp3");
|
|
@@ -2926,18 +3101,18 @@ describe("call-controller", () => {
|
|
|
2926
3101
|
controller.destroy();
|
|
2927
3102
|
});
|
|
2928
3103
|
|
|
2929
|
-
test("returns synthesized path with deepgram provider", () => {
|
|
3104
|
+
test("returns synthesized path with deepgram provider", async () => {
|
|
2930
3105
|
const cfg = loadConfig();
|
|
2931
3106
|
cfg.services.tts.provider = "deepgram";
|
|
2932
3107
|
|
|
2933
|
-
const result = resolveCallTtsProvider();
|
|
3108
|
+
const result = await resolveCallTtsProvider();
|
|
2934
3109
|
expect(result.provider).not.toBeNull();
|
|
2935
3110
|
expect(result.provider!.id).toBe("deepgram");
|
|
2936
3111
|
expect(result.useSynthesizedPath).toBe(true);
|
|
2937
3112
|
expect(result.audioFormat).toBe("mp3");
|
|
2938
3113
|
});
|
|
2939
3114
|
|
|
2940
|
-
test("Deepgram does not apply fish-audio referenceId gate", () => {
|
|
3115
|
+
test("Deepgram does not apply fish-audio referenceId gate", async () => {
|
|
2941
3116
|
// Deepgram has no referenceId requirement. Verify the fish-audio
|
|
2942
3117
|
// config gate does not apply to deepgram resolution.
|
|
2943
3118
|
const cfg = loadConfig();
|
|
@@ -2945,7 +3120,7 @@ describe("call-controller", () => {
|
|
|
2945
3120
|
// fish-audio referenceId left empty — should not affect deepgram.
|
|
2946
3121
|
cfg.services.tts.providers["fish-audio"].referenceId = "";
|
|
2947
3122
|
|
|
2948
|
-
const result = resolveCallTtsProvider();
|
|
3123
|
+
const result = await resolveCallTtsProvider();
|
|
2949
3124
|
expect(result.provider).not.toBeNull();
|
|
2950
3125
|
expect(result.provider!.id).toBe("deepgram");
|
|
2951
3126
|
expect(result.useSynthesizedPath).toBe(true);
|
|
@@ -2960,9 +3135,11 @@ describe("call-controller", () => {
|
|
|
2960
3135
|
|
|
2961
3136
|
// Controller starts idle after construction
|
|
2962
3137
|
expect(controller.getState()).toBe("idle");
|
|
2963
|
-
const
|
|
3138
|
+
const onAccepted = mock(() => {});
|
|
3139
|
+
const result = controller.handleBargeIn(onAccepted);
|
|
2964
3140
|
|
|
2965
3141
|
expect(result).toBe(false);
|
|
3142
|
+
expect(onAccepted).not.toHaveBeenCalled();
|
|
2966
3143
|
// No end-of-turn token should have been sent (no interruption)
|
|
2967
3144
|
const endTokens = relay.sentTokens.filter(
|
|
2968
3145
|
(t) => t.last === true && t.token === "",
|
|
@@ -2972,49 +3149,246 @@ describe("call-controller", () => {
|
|
|
2972
3149
|
controller.destroy();
|
|
2973
3150
|
});
|
|
2974
3151
|
|
|
2975
|
-
test("handleBargeIn returns false
|
|
2976
|
-
//
|
|
2977
|
-
//
|
|
3152
|
+
test("handleBargeIn returns false and does not abort while still processing (no output yet)", async () => {
|
|
3153
|
+
// Simulate a turn stuck waiting for the processing lock: no tokens
|
|
3154
|
+
// emitted, no completion. The controller must stay in `processing` and
|
|
3155
|
+
// NOT flip to `speaking`, so barge-in can't abort a silent turn.
|
|
2978
3156
|
mockStartVoiceTurn.mockImplementation(
|
|
2979
3157
|
async (opts: {
|
|
2980
3158
|
onTextDelta: (t: string) => void;
|
|
2981
3159
|
onComplete: () => void;
|
|
2982
3160
|
signal?: AbortSignal;
|
|
2983
3161
|
}) => {
|
|
2984
|
-
//
|
|
3162
|
+
// Never emit tokens or complete — remain in the pre-speech phase.
|
|
2985
3163
|
return { turnId: "run-slow", abort: () => opts.onComplete() };
|
|
2986
3164
|
},
|
|
2987
3165
|
);
|
|
2988
3166
|
|
|
2989
3167
|
const { relay, controller } = setupController();
|
|
2990
|
-
// Kick off a turn (moves to speaking state)
|
|
2991
3168
|
const turnPromise = controller.handleCallerUtterance("Hello");
|
|
2992
3169
|
|
|
2993
3170
|
// Wait for microtasks to settle
|
|
2994
3171
|
for (let i = 0; i < 5; i++) await Promise.resolve();
|
|
2995
3172
|
|
|
2996
|
-
//
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
);
|
|
3010
|
-
expect(endTokens.length).toBeGreaterThan(0);
|
|
3011
|
-
}
|
|
3173
|
+
// No outbound audio/tokens yet → still processing.
|
|
3174
|
+
expect(controller.getState()).toBe("processing");
|
|
3175
|
+
|
|
3176
|
+
const onAccepted = mock(() => {});
|
|
3177
|
+
const bargeResult = controller.handleBargeIn(onAccepted);
|
|
3178
|
+
expect(bargeResult).toBe(false);
|
|
3179
|
+
expect(onAccepted).not.toHaveBeenCalled();
|
|
3180
|
+
// Still processing (not aborted), and no interrupt/end-of-turn token sent.
|
|
3181
|
+
expect(controller.getState()).toBe("processing");
|
|
3182
|
+
const endTokens = relay.sentTokens.filter(
|
|
3183
|
+
(t) => t.last === true && t.token === "",
|
|
3184
|
+
);
|
|
3185
|
+
expect(endTokens.length).toBe(0);
|
|
3012
3186
|
|
|
3013
3187
|
// Cleanup: abort the pending turn
|
|
3014
3188
|
controller.destroy();
|
|
3015
3189
|
await turnPromise.catch(() => {});
|
|
3016
3190
|
});
|
|
3017
3191
|
|
|
3192
|
+
test("stays in processing until first token, then flips to speaking and barge-in is accepted", async () => {
|
|
3193
|
+
// Gate the first token so we can observe the pre-speech processing phase
|
|
3194
|
+
// (e.g. the lock-wait / generation window) before any audio is emitted.
|
|
3195
|
+
let emitFirstToken!: () => void;
|
|
3196
|
+
const firstTokenGate = new Promise<void>((r) => {
|
|
3197
|
+
emitFirstToken = r;
|
|
3198
|
+
});
|
|
3199
|
+
let releaseTurn!: () => void;
|
|
3200
|
+
const completeGate = new Promise<void>((r) => {
|
|
3201
|
+
releaseTurn = r;
|
|
3202
|
+
});
|
|
3203
|
+
|
|
3204
|
+
mockStartVoiceTurn.mockImplementation(
|
|
3205
|
+
async (opts: {
|
|
3206
|
+
onTextDelta: (t: string) => void;
|
|
3207
|
+
onComplete: () => void;
|
|
3208
|
+
signal?: AbortSignal;
|
|
3209
|
+
}) => {
|
|
3210
|
+
await firstTokenGate;
|
|
3211
|
+
if (opts.signal?.aborted) {
|
|
3212
|
+
const err = new Error("aborted");
|
|
3213
|
+
err.name = "AbortError";
|
|
3214
|
+
throw err;
|
|
3215
|
+
}
|
|
3216
|
+
opts.onTextDelta("Hello");
|
|
3217
|
+
// Hold the turn open so state remains `speaking` until we barge in.
|
|
3218
|
+
opts.signal?.addEventListener("abort", () => releaseTurn());
|
|
3219
|
+
await completeGate;
|
|
3220
|
+
opts.onComplete();
|
|
3221
|
+
return { turnId: "run-gate", abort: () => releaseTurn() };
|
|
3222
|
+
},
|
|
3223
|
+
);
|
|
3224
|
+
|
|
3225
|
+
const { controller } = setupController();
|
|
3226
|
+
const turnPromise = controller.handleCallerUtterance("Hi");
|
|
3227
|
+
for (let i = 0; i < 5; i++) await Promise.resolve();
|
|
3228
|
+
|
|
3229
|
+
// Before any token: processing, barge-in ignored (turn not aborted).
|
|
3230
|
+
expect(controller.getState()).toBe("processing");
|
|
3231
|
+
expect(controller.handleBargeIn()).toBe(false);
|
|
3232
|
+
expect(controller.getState()).toBe("processing");
|
|
3233
|
+
|
|
3234
|
+
// Release the first token → controller flips to speaking.
|
|
3235
|
+
emitFirstToken();
|
|
3236
|
+
for (let i = 0; i < 5; i++) await Promise.resolve();
|
|
3237
|
+
expect(controller.getState()).toBe("speaking");
|
|
3238
|
+
|
|
3239
|
+
// Now barge-in is accepted and interrupts the turn.
|
|
3240
|
+
expect(controller.handleBargeIn()).toBe(true);
|
|
3241
|
+
expect(controller.getState()).toBe("idle");
|
|
3242
|
+
|
|
3243
|
+
controller.destroy();
|
|
3244
|
+
await turnPromise.catch(() => {});
|
|
3245
|
+
});
|
|
3246
|
+
|
|
3247
|
+
test("buffering transport (media-stream): streamed tokens do not flip to speaking; barge-in is rejected and the turn still delivers", async () => {
|
|
3248
|
+
// Simulates the media-stream transport: sendTextToken(.., false)
|
|
3249
|
+
// only buffers; audio starts later. The controller must stay in
|
|
3250
|
+
// `processing` until the transport's audio-start signal fires, so
|
|
3251
|
+
// VAD speech-start during generation cannot abort a silent turn.
|
|
3252
|
+
let releaseTurn!: () => void;
|
|
3253
|
+
const completeGate = new Promise<void>((r) => {
|
|
3254
|
+
releaseTurn = r;
|
|
3255
|
+
});
|
|
3256
|
+
|
|
3257
|
+
mockStartVoiceTurn.mockImplementation(
|
|
3258
|
+
async (opts: {
|
|
3259
|
+
onTextDelta: (t: string) => void;
|
|
3260
|
+
onComplete: () => void;
|
|
3261
|
+
signal?: AbortSignal;
|
|
3262
|
+
}) => {
|
|
3263
|
+
opts.onTextDelta("Hello");
|
|
3264
|
+
opts.onTextDelta(" caller");
|
|
3265
|
+
opts.signal?.addEventListener("abort", () => releaseTurn());
|
|
3266
|
+
await completeGate;
|
|
3267
|
+
opts.onComplete();
|
|
3268
|
+
return { turnId: "run-buffered", abort: () => releaseTurn() };
|
|
3269
|
+
},
|
|
3270
|
+
);
|
|
3271
|
+
|
|
3272
|
+
const { relay, controller } = setupController();
|
|
3273
|
+
let audioStartCallback: (() => void) | null = null;
|
|
3274
|
+
relay.setAudioStartCallback = (cb) => {
|
|
3275
|
+
audioStartCallback = cb;
|
|
3276
|
+
};
|
|
3277
|
+
|
|
3278
|
+
const turnPromise = controller.handleCallerUtterance("Hi");
|
|
3279
|
+
for (let i = 0; i < 10; i++) await Promise.resolve();
|
|
3280
|
+
|
|
3281
|
+
// Tokens were emitted (buffered by the transport) but no audio has
|
|
3282
|
+
// started — the controller must still be processing.
|
|
3283
|
+
expect(
|
|
3284
|
+
relay.sentTokens.filter((t) => !t.last && t.token.length > 0).length,
|
|
3285
|
+
).toBeGreaterThan(0);
|
|
3286
|
+
expect(controller.getState()).toBe("processing");
|
|
3287
|
+
expect(audioStartCallback).not.toBeNull();
|
|
3288
|
+
|
|
3289
|
+
// VAD speech-start mid-generation → barge-in rejected, turn intact.
|
|
3290
|
+
expect(controller.handleBargeIn()).toBe(false);
|
|
3291
|
+
expect(controller.getState()).toBe("processing");
|
|
3292
|
+
|
|
3293
|
+
// The turn completes and delivers its end-of-turn signal.
|
|
3294
|
+
releaseTurn();
|
|
3295
|
+
await turnPromise;
|
|
3296
|
+
const endTokens = relay.sentTokens.filter(
|
|
3297
|
+
(t) => t.last === true && t.token === "",
|
|
3298
|
+
);
|
|
3299
|
+
expect(endTokens.length).toBe(1);
|
|
3300
|
+
|
|
3301
|
+
controller.destroy();
|
|
3302
|
+
});
|
|
3303
|
+
|
|
3304
|
+
test("buffering transport: audio-start signal flips to speaking and barge-in is accepted", async () => {
|
|
3305
|
+
let releaseTurn!: () => void;
|
|
3306
|
+
const completeGate = new Promise<void>((r) => {
|
|
3307
|
+
releaseTurn = r;
|
|
3308
|
+
});
|
|
3309
|
+
|
|
3310
|
+
mockStartVoiceTurn.mockImplementation(
|
|
3311
|
+
async (opts: {
|
|
3312
|
+
onTextDelta: (t: string) => void;
|
|
3313
|
+
onComplete: () => void;
|
|
3314
|
+
signal?: AbortSignal;
|
|
3315
|
+
}) => {
|
|
3316
|
+
opts.onTextDelta("Hello");
|
|
3317
|
+
opts.signal?.addEventListener("abort", () => releaseTurn());
|
|
3318
|
+
await completeGate;
|
|
3319
|
+
opts.onComplete();
|
|
3320
|
+
return { turnId: "run-buffered-2", abort: () => releaseTurn() };
|
|
3321
|
+
},
|
|
3322
|
+
);
|
|
3323
|
+
|
|
3324
|
+
const { relay, controller } = setupController();
|
|
3325
|
+
let audioStartCallback: (() => void) | null = null;
|
|
3326
|
+
let discardedPendingText = 0;
|
|
3327
|
+
relay.setAudioStartCallback = (cb) => {
|
|
3328
|
+
audioStartCallback = cb;
|
|
3329
|
+
};
|
|
3330
|
+
relay.discardPendingText = () => {
|
|
3331
|
+
discardedPendingText++;
|
|
3332
|
+
};
|
|
3333
|
+
|
|
3334
|
+
const turnPromise = controller.handleCallerUtterance("Hi");
|
|
3335
|
+
for (let i = 0; i < 10; i++) await Promise.resolve();
|
|
3336
|
+
expect(controller.getState()).toBe("processing");
|
|
3337
|
+
|
|
3338
|
+
// Transport reports real outbound audio started → speaking.
|
|
3339
|
+
audioStartCallback!();
|
|
3340
|
+
expect(controller.getState()).toBe("speaking");
|
|
3341
|
+
|
|
3342
|
+
// Barge-in now aborts the turn, discards the transport's buffered
|
|
3343
|
+
// text, and returns the controller to idle.
|
|
3344
|
+
expect(controller.handleBargeIn()).toBe(true);
|
|
3345
|
+
expect(controller.getState()).toBe("idle");
|
|
3346
|
+
expect(discardedPendingText).toBeGreaterThan(0);
|
|
3347
|
+
|
|
3348
|
+
controller.destroy();
|
|
3349
|
+
await turnPromise.catch(() => {});
|
|
3350
|
+
});
|
|
3351
|
+
|
|
3352
|
+
test("buffering transport: stale audio-start signal from a superseded run does not flip state", async () => {
|
|
3353
|
+
let releaseTurn!: () => void;
|
|
3354
|
+
const completeGate = new Promise<void>((r) => {
|
|
3355
|
+
releaseTurn = r;
|
|
3356
|
+
});
|
|
3357
|
+
|
|
3358
|
+
mockStartVoiceTurn.mockImplementation(
|
|
3359
|
+
async (opts: {
|
|
3360
|
+
onTextDelta: (t: string) => void;
|
|
3361
|
+
onComplete: () => void;
|
|
3362
|
+
signal?: AbortSignal;
|
|
3363
|
+
}) => {
|
|
3364
|
+
opts.onTextDelta("Hello");
|
|
3365
|
+
opts.signal?.addEventListener("abort", () => releaseTurn());
|
|
3366
|
+
await completeGate;
|
|
3367
|
+
opts.onComplete();
|
|
3368
|
+
return { turnId: "run-buffered-3", abort: () => releaseTurn() };
|
|
3369
|
+
},
|
|
3370
|
+
);
|
|
3371
|
+
|
|
3372
|
+
const { relay, controller } = setupController();
|
|
3373
|
+
let audioStartCallback: (() => void) | null = null;
|
|
3374
|
+
relay.setAudioStartCallback = (cb) => {
|
|
3375
|
+
audioStartCallback = cb;
|
|
3376
|
+
};
|
|
3377
|
+
|
|
3378
|
+
const turnPromise = controller.handleCallerUtterance("Hi");
|
|
3379
|
+
for (let i = 0; i < 10; i++) await Promise.resolve();
|
|
3380
|
+
const staleCallback = audioStartCallback!;
|
|
3381
|
+
|
|
3382
|
+
// Supersede the run (hard interrupt), then fire the stale signal.
|
|
3383
|
+
controller.handleInterrupt();
|
|
3384
|
+
expect(controller.getState()).toBe("idle");
|
|
3385
|
+
staleCallback();
|
|
3386
|
+
expect(controller.getState()).toBe("idle");
|
|
3387
|
+
|
|
3388
|
+
controller.destroy();
|
|
3389
|
+
await turnPromise.catch(() => {});
|
|
3390
|
+
});
|
|
3391
|
+
|
|
3018
3392
|
test("handleBargeIn returns true and interrupts when controller is speaking", async () => {
|
|
3019
3393
|
// Create a turn that holds the speaking state long enough to test barge-in
|
|
3020
3394
|
let resolveComplete: () => void;
|
|
@@ -3040,7 +3414,7 @@ describe("call-controller", () => {
|
|
|
3040
3414
|
},
|
|
3041
3415
|
);
|
|
3042
3416
|
|
|
3043
|
-
const { controller } = setupController();
|
|
3417
|
+
const { relay, controller } = setupController();
|
|
3044
3418
|
const turnPromise = controller.handleCallerUtterance("Hi");
|
|
3045
3419
|
|
|
3046
3420
|
// Let microtasks settle so onTextDelta runs
|
|
@@ -3048,8 +3422,24 @@ describe("call-controller", () => {
|
|
|
3048
3422
|
|
|
3049
3423
|
expect(controller.getState()).toBe("speaking");
|
|
3050
3424
|
|
|
3051
|
-
|
|
3425
|
+
// onAccepted must fire after the speaking gate but BEFORE the
|
|
3426
|
+
// end-of-turn token from handleInterrupt, so a transport's queue
|
|
3427
|
+
// flush cannot wipe that mark.
|
|
3428
|
+
const endTokensAtAccept: number[] = [];
|
|
3429
|
+
const onAccepted = mock(() => {
|
|
3430
|
+
endTokensAtAccept.push(
|
|
3431
|
+
relay.sentTokens.filter((t) => t.last === true && t.token === "")
|
|
3432
|
+
.length,
|
|
3433
|
+
);
|
|
3434
|
+
});
|
|
3435
|
+
const result = controller.handleBargeIn(onAccepted);
|
|
3052
3436
|
expect(result).toBe(true);
|
|
3437
|
+
expect(onAccepted).toHaveBeenCalledTimes(1);
|
|
3438
|
+
expect(endTokensAtAccept).toEqual([0]);
|
|
3439
|
+
const endTokens = relay.sentTokens.filter(
|
|
3440
|
+
(t) => t.last === true && t.token === "",
|
|
3441
|
+
);
|
|
3442
|
+
expect(endTokens.length).toBe(1);
|
|
3053
3443
|
|
|
3054
3444
|
// After barge-in, controller should be idle
|
|
3055
3445
|
expect(controller.getState()).toBe("idle");
|