@vellumai/assistant 0.10.12 → 0.11.0-staging.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 +1 -1
- package/ARCHITECTURE.md +4 -5
- package/README.md +1 -1
- package/docs/architecture/memory.md +60 -23
- package/docs/error-handling.md +3 -3
- package/docs/guardian-request-flow.md +112 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +9 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +449 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +468 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/app.d.ts +28 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/app.d.ts.map +1 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/index.d.ts +2 -2
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/index.d.ts.map +1 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/index.js.map +1 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/message.d.ts +2 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/message.d.ts.map +1 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/package.json +1 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +9 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +449 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +468 -0
- package/node_modules/@vellumai/gateway-client/package.json +1 -1
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +6 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +34 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +9 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +449 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +468 -0
- package/openapi.yaml +246 -10
- package/package.json +2 -2
- package/src/__tests__/acp-session.test.ts +3 -3
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +3 -0
- package/src/__tests__/anthropic-provider.test.ts +40 -0
- package/src/__tests__/approval-cascade.test.ts +5 -5
- package/src/__tests__/approval-routes-http.test.ts +3 -3
- package/src/__tests__/ask-question-resolver.test.ts +207 -0
- package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +16 -16
- package/src/__tests__/assistant-event-hub-targeted.test.ts +13 -11
- package/src/__tests__/assistant-event-hub.test.ts +13 -11
- package/src/__tests__/assistant-event.test.ts +12 -9
- package/src/__tests__/assistant-feature-flag-guard.test.ts +3 -3
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +1 -1
- package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -58
- package/src/__tests__/assistant-stream-state.test.ts +17 -7
- package/src/__tests__/avatar-identity-sync.test.ts +3 -3
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -0
- package/src/__tests__/call-conversation-messages.test.ts +134 -18
- package/src/__tests__/call-start-guardian-guard.test.ts +27 -0
- package/src/__tests__/channel-approvals.test.ts +0 -27
- package/src/__tests__/channel-guardian.test.ts +85 -512
- package/src/__tests__/channel-readiness-slack-remote.test.ts +151 -3
- package/src/__tests__/channel-retry-sweep.test.ts +88 -1
- package/src/__tests__/channel-setup-panel-ack.test.ts +12 -9
- package/src/__tests__/chat-reveal-guard-priming.test.ts +31 -34
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +2 -2
- package/src/__tests__/compaction-events.test.ts +8 -8
- package/src/__tests__/config-loader-platform-defaults.test.ts +54 -28
- package/src/__tests__/config-model-image-provider.test.ts +20 -0
- package/src/__tests__/config-schema-memory-substrate.test.ts +306 -0
- package/src/__tests__/config-schema.test.ts +14 -2
- package/src/__tests__/config-set-platform-guard.test.ts +11 -12
- package/src/__tests__/config-sounds-sync.test.ts +4 -4
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +5 -0
- package/src/__tests__/context-search-memory-source.test.ts +1 -1
- package/src/__tests__/context-search-memory-v2-source.test.ts +6 -6
- package/src/__tests__/context-token-estimator.test.ts +54 -1
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +12 -3
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +13 -4
- package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +4 -4
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +22 -13
- package/src/__tests__/conversation-agent-loop.test.ts +39 -30
- package/src/__tests__/conversation-confirmation-signals.test.ts +14 -14
- package/src/__tests__/conversation-evictor.test.ts +49 -0
- package/src/__tests__/conversation-fork-crud.test.ts +1 -1
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +5 -5
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +5 -5
- package/src/__tests__/conversation-message-sync-tags.test.ts +4 -4
- package/src/__tests__/conversation-queue.test.ts +81 -81
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +8 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +8 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +10 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +29 -2
- package/src/__tests__/conversation-slash-queue.test.ts +11 -11
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -2
- package/src/__tests__/conversation-speed-override.test.ts +2 -2
- package/src/__tests__/conversation-summarize-route.test.ts +9 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +18 -16
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +17 -17
- package/src/__tests__/conversation-surfaces-app-open.test.ts +7 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +7 -7
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +9 -9
- package/src/__tests__/conversation-surfaces-standalone.test.ts +12 -12
- package/src/__tests__/conversation-surfaces-state-update.test.ts +8 -5
- package/src/__tests__/conversation-surfaces-table-action.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +46 -46
- package/src/__tests__/conversation-sync-tags.test.ts +5 -5
- package/src/__tests__/conversation-wait-for-idle.test.ts +2 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -3
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -3
- package/src/__tests__/copy-composer-tc-templates.test.ts +3 -131
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/daemon-assistant-events.test.ts +14 -15
- package/src/__tests__/db-test-helpers.ts +13 -27
- package/src/__tests__/disk-pressure-routes.test.ts +12 -3
- package/src/__tests__/document-tool-security.test.ts +95 -0
- package/src/__tests__/drain-kick-guard.test.ts +232 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +19 -6
- package/src/__tests__/emit-event-signal.test.ts +5 -6
- package/src/__tests__/encrypted-store-test-helpers.ts +10 -20
- package/src/__tests__/events-tail-route.test.ts +6 -4
- package/src/__tests__/feature-flag-test-helpers.ts +10 -20
- package/src/__tests__/file-edit-tool.test.ts +24 -4
- package/src/__tests__/file-list-tool.test.ts +2 -1
- package/src/__tests__/file-read-tool.test.ts +62 -10
- package/src/__tests__/file-write-tool.test.ts +24 -5
- package/src/__tests__/followup-tools.test.ts +58 -0
- package/src/__tests__/gateway-threshold-reader-mock.ts +99 -0
- package/src/__tests__/graph-extraction-event-date.test.ts +1 -1
- package/src/__tests__/guardian-question-mode.test.ts +42 -3
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +283 -0
- package/src/__tests__/guardian-vellum-destination-pinning.test.ts +221 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +10 -10
- package/src/__tests__/host-browser-proxy.test.ts +1 -1
- package/src/__tests__/host-file-edit-tool.test.ts +3 -3
- package/src/__tests__/host-file-read-tool.test.ts +2 -2
- package/src/__tests__/host-file-write-tool.test.ts +7 -3
- package/src/__tests__/host-shell-tool.test.ts +17 -5
- package/src/__tests__/host-transfer-pending-interactions.test.ts +8 -8
- package/src/__tests__/image-credentials.test.ts +65 -8
- package/src/__tests__/injection-block.test.ts +4 -4
- package/src/__tests__/injector-chain.test.ts +1 -1
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
- package/src/__tests__/injector-registry-order-guard.test.ts +4 -9
- package/src/__tests__/injector-v3-suppression.test.ts +6 -4
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +27 -28
- package/src/__tests__/list-messages-page-latest.test.ts +2 -2
- package/src/__tests__/llm-usage-store.test.ts +67 -4
- package/src/__tests__/log-redact.test.ts +18 -0
- package/src/__tests__/managed-profile-guard.test.ts +27 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +62 -11
- package/src/__tests__/memory-identity-context-parity.test.ts +1 -1
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +9 -1
- package/src/__tests__/memory-retrieval-hook.test.ts +4 -4
- package/src/__tests__/notification-decision-strategy.test.ts +115 -11
- package/src/__tests__/notification-deep-link.test.ts +12 -12
- package/src/__tests__/notification-vellum-adapter.test.ts +8 -8
- package/src/__tests__/outbound-slack-persistence.test.ts +8 -8
- package/src/__tests__/path-policy.test.ts +315 -0
- package/src/__tests__/pending-interactions-resolved-event.test.ts +6 -6
- package/src/__tests__/persistence-layering-guard.test.ts +2 -2
- package/src/__tests__/pkb-autoinject.test.ts +1 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -0
- package/src/__tests__/plugin-event-hub-facade.test.ts +22 -15
- package/src/__tests__/plugin-import-boundary-guard.test.ts +15 -15
- package/src/__tests__/processing-flag-persist-failure.test.ts +0 -4
- package/src/__tests__/provider-platform-proxy-integration.test.ts +0 -1
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +1 -1
- package/src/__tests__/request-secret-standalone-channel-gate.test.ts +5 -5
- package/src/__tests__/require-fresh-approval.test.ts +38 -38
- package/src/__tests__/run-due-schedules.test.ts +72 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +11 -10
- package/src/__tests__/runtime-events-sse.test.ts +1 -1
- package/src/__tests__/schedule-routes.test.ts +44 -5
- package/src/__tests__/schedule-store.test.ts +3 -3
- package/src/__tests__/schedule-tools.test.ts +176 -0
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +3 -3
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +3 -3
- package/src/__tests__/secret-response-routing.test.ts +3 -3
- package/src/__tests__/secret-routes-platform-proxy.test.ts +4 -7
- package/src/__tests__/secret-scanner.test.ts +36 -0
- package/src/__tests__/send-endpoint-busy.test.ts +7 -7
- package/src/__tests__/slack-required-scopes-mirror.test.ts +81 -0
- package/src/__tests__/starter-task-flow.test.ts +6 -3
- package/src/__tests__/subagent-call-site-routing.test.ts +2 -2
- package/src/__tests__/subagent-disposal.test.ts +40 -11
- package/src/__tests__/subagent-fork-notifications.test.ts +4 -4
- package/src/__tests__/subagent-fork-prompt-role.test.ts +3 -3
- package/src/__tests__/subagent-fork-spawn.test.ts +4 -4
- package/src/__tests__/subagent-manager-notify.test.ts +73 -14
- package/src/__tests__/subagent-notify-parent.test.ts +48 -4
- package/src/__tests__/subagent-spawn-and-await.test.ts +62 -14
- package/src/__tests__/subagent-tools.test.ts +64 -0
- package/src/__tests__/sync-message-contract.test.ts +3 -3
- package/src/__tests__/terminal-tools.test.ts +12 -3
- package/src/__tests__/test-preload.ts +9 -0
- package/src/__tests__/tool-approval-handler.test.ts +583 -43
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +7 -4
- package/src/__tests__/tool-executor.test.ts +116 -0
- package/src/__tests__/tool-grant-request-escalation.test.ts +5 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -29
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +5 -5
- package/src/__tests__/tool-start-timestamp.test.ts +4 -4
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -22
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +13 -13
- package/src/__tests__/ui-work-result-surface.test.ts +3 -3
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +6 -6
- package/src/__tests__/voice-session-bridge.test.ts +44 -44
- package/src/__tests__/workspace-migration-134-image-generation-mode-to-provider.test.ts +284 -0
- package/src/__tests__/workspace-policy.test.ts +399 -0
- package/src/acp/__tests__/client-handler.test.ts +3 -3
- package/src/acp/__tests__/session-manager-persistence.test.ts +10 -10
- package/src/acp/__tests__/session-manager-resume.test.ts +7 -7
- package/src/acp/client-handler.ts +3 -3
- package/src/acp/session-manager.ts +8 -8
- package/src/api/events/subagent-event.ts +2 -2
- package/src/api/responses/memory-v2-activation-log.ts +2 -2
- package/src/approvals/AGENTS.md +2 -0
- package/src/approvals/guardian-request-resolvers.ts +118 -24
- package/src/calls/__tests__/voice-control-protocol.test.ts +57 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +158 -4
- package/src/calls/__tests__/voice-triage-escalate.test.ts +3 -4
- package/src/calls/call-controller.ts +27 -27
- package/src/calls/call-conversation-messages.ts +58 -18
- package/src/calls/voice-control-protocol.ts +56 -0
- package/src/calls/voice-session-bridge.ts +156 -58
- package/src/calls/voice-triage-escalate.ts +2 -3
- package/src/channels/config.ts +8 -0
- package/src/channels/types.ts +1 -0
- package/src/cli/commands/config.ts +49 -13
- package/src/cli/commands/memory/index.help.ts +4 -2
- package/src/cli/commands/memory/memory-retrospective.ts +5 -0
- package/src/cli/lib/bundled-marketplace.json +12 -0
- package/src/cli.ts +2 -109
- package/src/config/__tests__/deployment-context-defaults.test.ts +9 -0
- package/src/config/__tests__/substrate-twin-shadowing.test.ts +191 -0
- package/src/config/assistant-feature-flags.ts +1 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +13 -7
- package/src/config/feature-flag-cache.ts +6 -16
- package/src/config/feature-flag-registry.json +14 -30
- package/src/config/loader.ts +7 -1
- package/src/config/memory-tier.ts +15 -10
- package/src/config/memory-v3-gate.test.ts +278 -1
- package/src/config/memory-v3-gate.ts +88 -26
- package/src/config/raw-config-utils.ts +1 -1
- package/src/config/schemas/__tests__/live-voice.test.ts +27 -16
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/live-voice.ts +33 -7
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-retrospective.ts +13 -0
- package/src/config/schemas/memory-substrate.ts +183 -0
- package/src/config/schemas/memory-v2.ts +3 -2
- package/src/config/schemas/memory-v3.ts +11 -4
- package/src/config/schemas/memory.ts +35 -1
- package/src/config/schemas/services.ts +10 -2
- package/src/config/substrate-twin-shadowing.ts +195 -0
- package/src/config/webhook-routing.ts +67 -0
- package/src/context/compactor.ts +10 -3
- package/src/context/token-estimator.ts +21 -0
- package/src/daemon/__tests__/channel-ui-capability.test.ts +85 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +3 -3
- package/src/daemon/__tests__/embedding-reconcile.test.ts +3 -3
- package/src/daemon/__tests__/inference-profile-notification.test.ts +3 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +5 -5
- package/src/daemon/__tests__/web-search-status-text.test.ts +2 -2
- package/src/daemon/channel-ui-capability.ts +70 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +4 -4
- package/src/daemon/conversation-agent-loop.ts +12 -5
- package/src/daemon/conversation-evictor.ts +26 -4
- package/src/daemon/conversation-launch.ts +11 -14
- package/src/daemon/conversation-messaging.ts +14 -14
- package/src/daemon/conversation-notifiers.ts +2 -2
- package/src/daemon/conversation-process.ts +133 -21
- package/src/daemon/conversation-queue-manager.ts +7 -7
- package/src/daemon/conversation-runtime-assembly.ts +16 -0
- package/src/daemon/conversation-store.ts +11 -3
- package/src/daemon/conversation-surfaces.ts +4 -4
- package/src/daemon/conversation-tool-setup.ts +13 -7
- package/src/daemon/conversation-usage.ts +12 -11
- package/src/daemon/conversation.ts +38 -15
- package/src/daemon/disk-pressure-guard.ts +5 -12
- package/src/daemon/embedding-reconcile.ts +14 -9
- package/src/daemon/handlers/config-channels.ts +40 -134
- package/src/daemon/handlers/config-model.ts +15 -7
- package/src/daemon/handlers/conversations.ts +4 -2
- package/src/daemon/handlers/shared.ts +29 -0
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/lifecycle.ts +13 -0
- package/src/daemon/message-protocol.ts +8 -144
- package/src/daemon/message-types/computer-use.ts +0 -12
- package/src/daemon/message-types/conversations.ts +9 -345
- package/src/daemon/message-types/host-app-control.ts +0 -9
- package/src/daemon/message-types/host-browser.ts +0 -7
- package/src/daemon/message-types/host-ui-snapshot.ts +0 -7
- package/src/daemon/message-types/messages.ts +0 -48
- package/src/daemon/message-types/notifications.ts +0 -7
- package/src/daemon/message-types/skills.ts +0 -5
- package/src/daemon/message-types/surfaces.ts +0 -9
- package/src/daemon/message-types/sync.ts +0 -2
- package/src/daemon/process-message.ts +5 -5
- package/src/daemon/skill-memory-refresh.ts +5 -5
- package/src/daemon/tool-side-effects.ts +13 -13
- package/src/daemon/trust-context.ts +1 -1
- package/src/daemon/wake-conversation-ops.ts +4 -4
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +100 -7
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +67 -0
- package/src/heartbeat/heartbeat-run-store.ts +81 -4
- package/src/heartbeat/heartbeat-service.ts +32 -1
- package/src/home/__tests__/feed-writer.test.ts +7 -7
- package/src/home/__tests__/home-content-refresh.test.ts +3 -0
- package/src/home/__tests__/suggested-prompts.test.ts +1 -0
- package/src/home/feed-writer.ts +6 -13
- package/src/home/home-content-refresh.ts +6 -9
- package/src/home/relationship-state-writer.ts +5 -12
- package/src/home/suggested-prompts-cache.ts +6 -19
- package/src/ipc/__tests__/events-publish-client.test.ts +67 -0
- package/src/ipc/events-publish-client.ts +44 -0
- package/src/ipc/routes/__tests__/conversation-sync-ipc-routes.test.ts +2 -2
- package/src/ipc/routes/__tests__/events-ipc-routes.test.ts +38 -9
- package/src/ipc/routes/events-ipc-routes.ts +11 -31
- package/src/live-voice/__tests__/front-decision.test.ts +4 -176
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +276 -68
- package/src/live-voice/__tests__/live-voice-events.test.ts +6 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +185 -46
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +24 -59
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +38 -54
- package/src/live-voice/__tests__/live-voice-vad.test.ts +218 -422
- package/src/live-voice/front-decision.ts +5 -155
- package/src/live-voice/live-voice-metrics.ts +4 -1
- package/src/live-voice/live-voice-session.ts +650 -333
- package/src/live-voice/progress-phrases.ts +17 -0
- package/src/live-voice/protocol.ts +15 -0
- package/src/media/app-icon-generator.ts +15 -8
- package/src/media/avatar-router.ts +9 -5
- package/src/media/image-credentials.ts +42 -10
- package/src/media/image-service.ts +1 -13
- package/src/media/types.ts +26 -3
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/__tests__/broadcaster.test.ts +95 -0
- package/src/notifications/adapters/macos.ts +3 -3
- package/src/notifications/broadcaster.ts +57 -0
- package/src/notifications/copy-composer.ts +0 -15
- package/src/notifications/decision-engine.ts +21 -22
- package/src/notifications/guardian-question-mode.ts +57 -2
- package/src/notifications/home-feed-side-effect.ts +4 -0
- package/src/notifications/signal.ts +6 -4
- package/src/notifications/trusted-contact-payloads.ts +3 -4
- package/src/notifications/vellum-card-affinity.ts +39 -0
- package/src/oauth/AGENTS.md +1 -1
- package/src/permissions/channel-permission-query.test.ts +69 -0
- package/src/permissions/channel-permission-query.ts +73 -1
- package/src/permissions/checker.test.ts +85 -9
- package/src/permissions/checker.ts +17 -22
- package/src/permissions/confirmation-guardian-request.test.ts +2 -2
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/gateway-threshold-reader.test.ts +224 -17
- package/src/permissions/gateway-threshold-reader.ts +82 -10
- package/src/permissions/ipc-risk-types.ts +8 -0
- package/src/permissions/prompter.ts +5 -5
- package/src/permissions/question-guardian-request.test.ts +222 -0
- package/src/permissions/question-guardian-request.ts +179 -0
- package/src/permissions/question-prompter.test.ts +8 -8
- package/src/permissions/question-prompter.ts +18 -0
- package/src/permissions/secret-prompter.ts +2 -4
- package/src/permissions/workspace-policy.ts +236 -34
- package/src/persistence/__tests__/lifecycle-quiesce.test.ts +82 -0
- package/src/persistence/__tests__/message-content-file.test.ts +58 -3
- package/src/persistence/conversation-crud.ts +93 -0
- package/src/persistence/conversation-key-store.ts +63 -0
- package/src/persistence/db-async-query.ts +1 -1
- package/src/persistence/db-singleton.ts +14 -27
- package/src/persistence/delivery-crud.ts +45 -0
- package/src/persistence/embeddings/embedding-backend.ts +2 -1
- package/src/persistence/embeddings/embedding-identity.ts +7 -3
- package/src/persistence/job-utils.ts +32 -27
- package/src/persistence/jobs-store.ts +92 -3
- package/src/persistence/lifecycle-quiesce.ts +80 -0
- package/src/persistence/llm-usage-store.ts +45 -8
- package/src/persistence/message-content-file.ts +4 -4
- package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +1 -1
- package/src/persistence/migrations/351-schedule-skill-script-handoff.ts +39 -0
- package/src/persistence/migrations/352-drop-schedule-skill-script-handoff.test.ts +83 -0
- package/src/persistence/migrations/352-drop-schedule-skill-script-handoff.ts +36 -0
- package/src/persistence/migrations/353-add-llm-usage-conversation-type.test.ts +110 -0
- package/src/persistence/migrations/353-add-llm-usage-conversation-type.ts +49 -0
- package/src/persistence/schema/infrastructure.ts +11 -0
- package/src/persistence/steps.ts +7 -1
- package/src/plugin-api/__tests__/publish-event-forwarding.test.ts +113 -0
- package/src/plugin-api/__tests__/publish-event.test.ts +57 -0
- package/src/plugin-api/conversation-turn.ts +4 -4
- package/src/plugin-api/event-hub-facade.ts +15 -6
- package/src/plugin-api/index.ts +10 -4
- package/src/plugin-api/publish-event.ts +36 -0
- package/src/plugins/AGENTS.md +2 -0
- package/src/plugins/defaults/compaction/window-manager.ts +9 -0
- package/src/plugins/defaults/index.ts +9 -13
- package/src/plugins/defaults/memory/AGENTS.md +775 -0
- package/src/plugins/defaults/memory/__tests__/capability-seed-embed-gate.test.ts +186 -0
- package/src/plugins/defaults/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +1 -1
- package/src/plugins/defaults/memory/__tests__/indexer-tier-dispatch.test.ts +129 -0
- package/src/plugins/defaults/memory/__tests__/jobs-worker-quiesce-gate.test.ts +91 -0
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v1-graph-substrate-noop.test.ts +342 -0
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +9 -1
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +36 -1
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-sweep-defer.test.ts +9 -1
- package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +2 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +44 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +151 -10
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1155 -0
- package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -1
- package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -3
- package/src/plugins/defaults/memory/__tests__/tools-delete-memory-page.test.ts +1 -1
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +71 -35
- package/src/plugins/defaults/memory/context-search/sources/memory.ts +5 -1
- package/src/plugins/defaults/memory/fork-conversation-memory.ts +4 -4
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +80 -10
- package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/capability-seed.ts +24 -10
- package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +123 -49
- package/src/plugins/defaults/memory/graph/graph-search.ts +6 -118
- package/src/plugins/defaults/memory/graph/in-context-tracker.test.ts +173 -0
- package/src/plugins/defaults/memory/graph/in-context-tracker.ts +109 -0
- package/src/plugins/defaults/memory/graph/store.ts +1 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +5 -2
- package/src/plugins/defaults/memory/graph-topology/__tests__/build-memory-graph.test.ts +1 -1
- package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +6 -9
- package/src/plugins/defaults/memory/hooks/__tests__/user-prompt-submit.test.ts +30 -16
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +18 -14
- package/src/plugins/defaults/memory/indexer.ts +146 -89
- package/src/plugins/defaults/memory/injectors.ts +86 -41
- package/src/plugins/defaults/memory/job-handlers.ts +130 -39
- package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +2 -2
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +22 -10
- package/src/plugins/defaults/memory/jobs-worker.ts +279 -208
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +4 -2
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +40 -3
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +99 -11
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-routes.test.ts +5 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +3 -4
- package/src/plugins/defaults/memory/src/memory-graph-routes.ts +2 -1
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +7 -5
- package/src/plugins/defaults/memory/src/memory-item-routes.ts +51 -19
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +31 -20
- package/src/plugins/defaults/memory/startup.ts +58 -21
- package/src/plugins/defaults/memory/substrate/__tests__/boot-maintenance.test.ts +335 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/cli-command-store.test.ts +14 -8
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/consolidation-job.test.ts +51 -21
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/edge-index.test.ts +1 -1
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/frontmatter-sweep.test.ts +3 -3
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/page-index.test.ts +1 -1
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/page-store.test.ts +1 -1
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/prompts-consolidation.test.ts +2 -2
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/qdrant.test.ts +23 -14
- package/src/plugins/defaults/memory/substrate/__tests__/reembed-job.test.ts +262 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/sim.test.ts +9 -12
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/skill-content.test.ts +2 -2
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/skill-store.test.ts +9 -9
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/sparse-bm25.test.ts +12 -5
- package/src/plugins/defaults/memory/substrate/__tests__/spread.test.ts +276 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/static-context.test.ts +17 -8
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/sweep-job.test.ts +9 -9
- package/src/plugins/defaults/memory/{v3/substrate/memory-v2-startup.ts → substrate/boot-maintenance.ts} +43 -24
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/cli-command-store.ts +13 -8
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/consolidation-job.ts +51 -27
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/constants.ts +1 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/edge-index.ts +28 -9
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/frontmatter-sweep.ts +14 -7
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/injected-block-slugs.ts +9 -3
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/page-index.ts +29 -10
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/page-store.ts +35 -12
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/prompts/consolidation.ts +14 -8
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/prompts/sweep.ts +4 -1
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/qdrant.ts +96 -36
- package/src/plugins/defaults/memory/substrate/reembed-job.ts +54 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/sim.ts +26 -15
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/skill-content.ts +1 -1
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/skill-store.ts +13 -8
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/sparse-bm25.ts +27 -10
- package/src/plugins/defaults/memory/substrate/spread.ts +165 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/static-context.ts +12 -7
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/sweep-job.ts +20 -15
- package/src/plugins/defaults/memory/substrate/tuning.ts +93 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/types.ts +7 -4
- package/src/plugins/defaults/memory/tools.test.ts +1 -1
- package/src/plugins/defaults/memory/tools.ts +1 -1
- package/src/plugins/defaults/memory/v1/README.md +5 -0
- package/src/plugins/defaults/memory/{__tests__ → v1/__tests__}/filing-jobs.test.ts +4 -4
- package/src/plugins/defaults/memory/{filing-jobs.ts → v1/filing-jobs.ts} +3 -3
- package/src/plugins/defaults/memory/{graph → v1/graph}/bootstrap.test.ts +68 -6
- package/src/plugins/defaults/memory/{graph → v1/graph}/bootstrap.ts +40 -21
- package/src/plugins/defaults/memory/{graph → v1/graph}/consolidation.ts +14 -14
- package/src/plugins/defaults/memory/{graph → v1/graph}/decay.test.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/decay.ts +3 -3
- package/src/plugins/defaults/memory/{graph → v1/graph}/extraction-job.ts +5 -5
- package/src/plugins/defaults/memory/{graph → v1/graph}/extraction.test.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/extraction.ts +20 -14
- package/src/plugins/defaults/memory/{graph → v1/graph}/graph-search.test.ts +3 -3
- package/src/plugins/defaults/memory/v1/graph/graph-search.ts +123 -0
- package/src/plugins/defaults/memory/{graph → v1/graph}/injection.test.ts +1 -118
- package/src/plugins/defaults/memory/{graph → v1/graph}/injection.ts +9 -111
- package/src/plugins/defaults/memory/{graph → v1/graph}/narrative.ts +6 -6
- package/src/plugins/defaults/memory/{graph → v1/graph}/pattern-scan.ts +5 -5
- package/src/plugins/defaults/memory/{graph → v1/graph}/retriever.test.ts +135 -42
- package/src/plugins/defaults/memory/{graph → v1/graph}/retriever.ts +40 -22
- package/src/plugins/defaults/memory/{graph → v1/graph}/scoring.test.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/scoring.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/serendipity.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/triggers.test.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/triggers.ts +1 -1
- package/src/plugins/defaults/memory/{identity-context.ts → v1/identity-context.ts} +4 -4
- package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/backfill.ts +6 -6
- package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/embedding.test.ts +6 -6
- package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/embedding.ts +6 -6
- package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/index-maintenance.ts +8 -8
- package/src/plugins/defaults/memory/{jobs → v1/jobs}/embed-pkb-file.test.ts +7 -7
- package/src/plugins/defaults/memory/{jobs → v1/jobs}/embed-pkb-file.ts +5 -5
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/autoinject.ts +1 -1
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/context.ts +2 -2
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-index.test.ts +3 -3
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-index.ts +2 -2
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-reconcile.test.ts +3 -3
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-reconcile.ts +2 -2
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-search.test.ts +3 -3
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-search.ts +6 -6
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/types.ts +3 -5
- package/src/plugins/defaults/memory/{pkb-schedule.ts → v1/pkb-schedule.ts} +2 -2
- package/src/plugins/defaults/memory/{search/semantic.ts → v1/semantic-search.ts} +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/activation-store.test.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/activation.test.ts +11 -274
- package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +9 -131
- package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +14 -10
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +7 -5
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +12 -6
- package/src/plugins/defaults/memory/{memory-v2-activation-log-store.ts → v2/activation-log-store.ts} +2 -2
- package/src/plugins/defaults/memory/v2/activation-store.ts +22 -8
- package/src/plugins/defaults/memory/v2/activation.ts +30 -163
- package/src/plugins/defaults/memory/v2/backfill-jobs.ts +7 -54
- package/src/plugins/defaults/memory/{memory-v2-concept-frequency.ts → v2/concept-frequency.ts} +3 -3
- package/src/plugins/defaults/memory/v2/harness/oracle.ts +1 -1
- package/src/plugins/defaults/memory/v2/harness/replay-input.ts +2 -2
- package/src/plugins/defaults/memory/v2/harness/retriever.ts +1 -1
- package/src/plugins/defaults/memory/v2/injection.ts +36 -26
- package/src/plugins/defaults/memory/v2/migration.ts +2 -2
- package/src/plugins/defaults/memory/{rerank-local.ts → v2/rerank-local.ts} +44 -18
- package/src/plugins/defaults/memory/v2/reranker.ts +29 -11
- package/src/plugins/defaults/memory/v2/router.ts +38 -15
- package/src/plugins/defaults/memory/v3/__tests__/candidate-match.test.ts +5 -3
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +21 -8
- package/src/plugins/defaults/memory/v3/__tests__/edge.test.ts +1 -1
- package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +1 -1
- package/src/plugins/defaults/memory/v3/__tests__/maintain-job.test.ts +4 -1
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +181 -3
- package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +3 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +14 -5
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +40 -50
- package/src/plugins/defaults/memory/v3/__tests__/tuning-profile.test.ts +1 -0
- package/src/plugins/defaults/memory/v3/candidate-match.ts +2 -2
- package/src/plugins/defaults/memory/v3/capabilities.ts +6 -4
- package/src/plugins/defaults/memory/v3/card.ts +25 -9
- package/src/plugins/defaults/memory/v3/edge.ts +52 -18
- package/src/plugins/defaults/memory/v3/gate.ts +4 -4
- package/src/plugins/defaults/memory/v3/injector.ts +10 -3
- package/src/plugins/defaults/memory/v3/maintain-job.ts +3 -3
- package/src/plugins/defaults/memory/v3/orchestrate.ts +100 -18
- package/src/plugins/defaults/memory/v3/page-content.ts +26 -10
- package/src/plugins/defaults/memory/v3/prune.ts +40 -16
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +34 -12
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +9 -13
- package/src/plugins/defaults/memory/v3/span-query.test.ts +86 -0
- package/src/plugins/defaults/memory/v3/span-query.ts +71 -0
- package/src/plugins/defaults/memory/v3/tuning-profile.ts +3 -0
- package/src/plugins/defaults/memory/v3/types.ts +4 -1
- package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +2 -2
- package/src/plugins/defaults/memory/validation.ts +1 -1
- package/src/plugins/defaults/memory/worker.ts +2 -1
- package/src/plugins/defaults/platform-hosted/__tests__/reengage.test.ts +104 -12
- package/src/plugins/defaults/platform-hosted/routes/reengage.ts +104 -12
- package/src/plugins/types.ts +2 -2
- package/src/providers/__tests__/registry-native-web-search.test.ts +47 -2
- package/src/providers/anthropic/client.ts +15 -6
- package/src/providers/content-block-schema.ts +26 -0
- package/src/providers/inference/__tests__/connections-credential-normalize.test.ts +87 -0
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/inference/connections.ts +30 -14
- package/src/providers/model-catalog.ts +77 -0
- package/src/providers/openai/__tests__/chat-completions-input-audio.test.ts +260 -0
- package/src/providers/openai/chat-completions-provider.ts +65 -17
- package/src/providers/openai/input-audio.ts +76 -0
- package/src/providers/openrouter/client.ts +0 -2
- package/src/providers/platform-proxy/constants.ts +4 -0
- package/src/providers/poolside/client.ts +42 -0
- package/src/providers/registry.ts +40 -8
- package/src/providers/retry.ts +2 -0
- package/src/providers/types.ts +39 -1
- package/src/providers/vercel-ai-gateway/client.ts +0 -2
- package/src/runtime/AGENTS.md +7 -5
- package/src/runtime/__tests__/question-request-guardian-bridge.test.ts +173 -0
- package/src/runtime/__tests__/question-resolution.test.ts +111 -0
- package/src/runtime/access-request-helper.ts +7 -15
- package/src/runtime/assistant-event-hub.ts +43 -31
- package/src/runtime/assistant-event-publish-options.ts +31 -0
- package/src/runtime/assistant-event.ts +19 -30
- package/src/runtime/assistant-stream-state.ts +4 -4
- package/src/runtime/channel-approvals.ts +0 -25
- package/src/runtime/channel-readiness-service.ts +99 -40
- package/src/runtime/channel-retry-sweep.ts +38 -15
- package/src/runtime/confirmation-request-guardian-bridge.ts +4 -0
- package/src/runtime/guardian-reply-router.ts +108 -0
- package/src/runtime/http-server.ts +8 -0
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/process-role.ts +42 -0
- package/src/runtime/question-request-guardian-bridge.ts +184 -0
- package/src/runtime/question-resolution.ts +153 -0
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +6 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -1
- package/src/runtime/routes/acp-routes.ts +2 -2
- package/src/runtime/routes/bookmark-routes.ts +3 -15
- package/src/runtime/routes/canned-message-complete.ts +2 -2
- package/src/runtime/routes/consolidation-routes.ts +8 -2
- package/src/runtime/routes/conversation-list-routes.ts +3 -19
- package/src/runtime/routes/conversation-management-routes.ts +1 -5
- package/src/runtime/routes/conversation-query-routes.ts +52 -8
- package/src/runtime/routes/conversation-routes.ts +36 -22
- package/src/runtime/routes/credential-request-routes.ts +0 -1
- package/src/runtime/routes/filing-routes.ts +7 -7
- package/src/runtime/routes/global-search-routes.ts +1 -1
- package/src/runtime/routes/guardian-approval-prompt.ts +2 -2
- package/src/runtime/routes/image-generation-routes.ts +12 -9
- package/src/runtime/routes/inbound-message-handler.ts +17 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +50 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +19 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -4
- package/src/runtime/routes/inbound-stages/inbound-content-prep.test.ts +129 -0
- package/src/runtime/routes/inbound-stages/inbound-content-prep.ts +141 -9
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/lifecycle-routes.ts +199 -0
- package/src/runtime/routes/platform-routes.ts +3 -8
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +7 -7
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +5 -5
- package/src/runtime/routes/question-routes.ts +34 -87
- package/src/runtime/routes/secret-routes.ts +11 -1
- package/src/runtime/routes/settings-routes.ts +8 -18
- package/src/runtime/routes/upgrade-broadcast-routes.ts +4 -5
- package/src/runtime/routes/workspace-routes.test.ts +7 -4
- package/src/runtime/slack-reply-session.test.ts +12 -12
- package/src/runtime/slack-reply-session.ts +17 -17
- package/src/runtime/sync/sync-publisher.test.ts +7 -5
- package/src/runtime/tool-grant-request-helper.ts +4 -0
- package/src/schedule/schedule-store.ts +70 -0
- package/src/schedule/scheduler.ts +27 -0
- package/src/security/AGENTS.md +1 -1
- package/src/security/credential-key.ts +17 -0
- package/src/security/secret-ingress.ts +14 -105
- package/src/security/secret-patterns.ts +8 -139
- package/src/security/secret-scanner.ts +8 -3
- package/src/security/store-path-override.ts +6 -16
- package/src/sequence/store.ts +8 -0
- package/src/signals/emit-event.ts +5 -10
- package/src/signals/event-stream.ts +20 -10
- package/src/subagent/manager.ts +76 -25
- package/src/subagent/notify.ts +8 -0
- package/src/subagent/types.ts +13 -3
- package/src/telegram/__tests__/webhook-health.test.ts +442 -0
- package/src/telegram/webhook-health.ts +478 -0
- package/src/telemetry/AGENTS.md +6 -0
- package/src/telemetry/memory-corpus-size.test.ts +129 -0
- package/src/telemetry/memory-corpus-size.ts +184 -0
- package/src/telemetry/memory-tier-reporter.test.ts +67 -2
- package/src/telemetry/memory-tier-reporter.ts +30 -3
- package/src/tools/__tests__/document-tool-input-schemas.test.ts +103 -0
- package/src/tools/__tests__/followup-call-acp-tool-input-schemas.test.ts +125 -0
- package/src/tools/__tests__/schedule-tool-input-schemas.test.ts +83 -0
- package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +116 -0
- package/src/tools/__tests__/tool-input-schemas.test.ts +192 -0
- package/src/tools/__tests__/tool-schema-drift-harness.ts +142 -0
- package/src/tools/acp/abort.ts +21 -1
- package/src/tools/acp/context.ts +5 -5
- package/src/tools/acp/spawn.test.ts +15 -5
- package/src/tools/acp/spawn.ts +26 -3
- package/src/tools/acp/status.ts +20 -1
- package/src/tools/acp/steer.test.ts +20 -0
- package/src/tools/acp/steer.ts +23 -2
- package/src/tools/ask-question/ask-question-tool.test.ts +65 -0
- package/src/tools/ask-question/ask-question-tool.ts +68 -92
- package/src/tools/browser/browser-screencast.ts +6 -6
- package/src/tools/calls/call-end.ts +24 -3
- package/src/tools/calls/call-start.ts +35 -11
- package/src/tools/calls/call-status.ts +20 -1
- package/src/tools/document/document-comment-tool.test.ts +49 -0
- package/src/tools/document/document-comment-tool.ts +45 -6
- package/src/tools/document/document-tool.ts +142 -24
- package/src/tools/executor.ts +27 -13
- package/src/tools/filesystem/edit.ts +62 -60
- package/src/tools/filesystem/list.ts +36 -29
- package/src/tools/filesystem/read.ts +56 -39
- package/src/tools/filesystem/write.ts +44 -38
- package/src/tools/followups/followup_create.ts +41 -12
- package/src/tools/followups/followup_list.ts +34 -6
- package/src/tools/followups/followup_resolve.ts +26 -4
- package/src/tools/host-filesystem/edit.ts +50 -60
- package/src/tools/host-filesystem/read.ts +45 -39
- package/src/tools/host-filesystem/write.ts +31 -36
- package/src/tools/host-terminal/host-shell.ts +76 -62
- package/src/tools/policy-context.ts +27 -10
- package/src/tools/schedule/create.ts +76 -33
- package/src/tools/schedule/delete.ts +21 -2
- package/src/tools/schedule/list.ts +25 -3
- package/src/tools/schedule/update.ts +105 -48
- package/src/tools/shared/filesystem/path-policy.ts +246 -40
- package/src/tools/shared/zod-tool-schema.ts +117 -0
- package/src/tools/subagent/abort.ts +12 -4
- package/src/tools/subagent/consult-deadline.ts +9 -3
- package/src/tools/subagent/message.ts +21 -5
- package/src/tools/subagent/notify-parent.ts +44 -23
- package/src/tools/subagent/read.ts +15 -4
- package/src/tools/subagent/resolve.ts +26 -4
- package/src/tools/subagent/spawn.ts +37 -13
- package/src/tools/subagent/status.ts +12 -4
- package/src/tools/system/request-permission.ts +35 -26
- package/src/tools/terminal/shell.ts +68 -55
- package/src/tools/tool-approval-handler.ts +327 -53
- package/src/tools/tool-input-schemas.ts +85 -0
- package/src/tools/tool-types.ts +7 -0
- package/src/tools/types.ts +8 -0
- package/src/tools/workflows/manage-workflows.ts +30 -2
- package/src/tools/workflows/run-workflow.test.ts +54 -2
- package/src/tools/workflows/run-workflow.ts +32 -4
- package/src/util/fs-symlinks.ts +30 -0
- package/src/util/log-redact.ts +7 -3
- package/src/vellum-assistant-namespace.d.ts +80 -0
- package/src/watcher/watcher-store.ts +8 -0
- package/src/workflows/leaf-runner.test.ts +104 -2
- package/src/workflows/leaf-runner.ts +31 -6
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +2 -2
- package/src/workspace/migrations/134-image-generation-mode-to-provider.ts +128 -0
- package/src/workspace/migrations/135-copy-substrate-tunables.ts +248 -0
- package/src/workspace/migrations/__tests__/135-copy-substrate-tunables.test.ts +446 -0
- package/src/workspace/migrations/__tests__/backfill-v3-leaves.test.ts +2 -2
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/memory-retrieval-budget.test.ts +0 -49
- package/src/__tests__/secret-patterns.test.ts +0 -27
- package/src/config/__tests__/proc-to-skills.test.ts +0 -21
- package/src/config/memory-tier.test.ts +0 -44
- package/src/daemon/message-types/acp.ts +0 -20
- package/src/daemon/message-types/apps.ts +0 -11
- package/src/daemon/message-types/background-tools.ts +0 -14
- package/src/daemon/message-types/bookmarks.ts +0 -13
- package/src/daemon/message-types/contacts.ts +0 -13
- package/src/daemon/message-types/document-comments.ts +0 -14
- package/src/daemon/message-types/documents.ts +0 -13
- package/src/daemon/message-types/home.ts +0 -15
- package/src/daemon/message-types/host-bash.ts +0 -12
- package/src/daemon/message-types/host-cu.ts +0 -10
- package/src/daemon/message-types/host-file.ts +0 -12
- package/src/daemon/message-types/host-transfer.ts +0 -12
- package/src/daemon/message-types/integrations.ts +0 -90
- package/src/daemon/message-types/memory.ts +0 -10
- package/src/daemon/message-types/schedules.ts +0 -14
- package/src/daemon/message-types/settings.ts +0 -17
- package/src/daemon/message-types/subagents.ts +0 -63
- package/src/daemon/message-types/upgrades.ts +0 -14
- package/src/daemon/message-types/workflows.ts +0 -25
- package/src/daemon/message-types/workspace.ts +0 -137
- package/src/live-voice/ack-phrases.ts +0 -58
- package/src/plugins/defaults/memory/retrieval-budget.ts +0 -29
- package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +0 -69
- package/src/plugins/defaults/memory/v3/gate-flag.ts +0 -20
- package/src/plugins/defaults/memory/v3/substrate/__tests__/memory-v2-startup.test.ts +0 -86
- package/src/plugins/defaults/memory/v3/substrate/memory-v2-startup.test.ts +0 -205
- /package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/concept-page-frontmatter-schema.test.ts +0 -0
- /package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/consolidation-prompt-flag-gating-guard.test.ts +0 -0
- /package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/injected-block-slugs.test.ts +0 -0
- /package/src/plugins/defaults/memory/{v3/substrate → substrate}/cli-command-content.ts +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
1
|
+
import { afterEach, beforeAll, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import type { TurnDetectorConfig } from "../../calls/media-turn-detector.js";
|
|
4
4
|
import type {
|
|
@@ -19,10 +19,8 @@ import type {
|
|
|
19
19
|
StreamingTranscriber,
|
|
20
20
|
SttStreamServerEvent,
|
|
21
21
|
} from "../../stt/types.js";
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
VoiceFrontDecider,
|
|
25
|
-
} from "../front-decision.js";
|
|
22
|
+
import { __resetRegistryForTesting } from "../../tools/registry.js";
|
|
23
|
+
import type { VoiceFrontDecider } from "../front-decision.js";
|
|
26
24
|
import type { LiveVoiceAudioArchiveResult } from "../live-voice-archive.js";
|
|
27
25
|
import {
|
|
28
26
|
createLiveVoiceSession,
|
|
@@ -314,6 +312,11 @@ async function flushAsyncCallbacks(): Promise<void> {
|
|
|
314
312
|
}
|
|
315
313
|
|
|
316
314
|
describe("LiveVoiceSession server VAD", () => {
|
|
315
|
+
// The foreground-wins classification consults the tool registry's owner map
|
|
316
|
+
// (a tool is only provably read-only when it is the trusted built-in);
|
|
317
|
+
// register the core baseline so built-in names resolve like in the daemon.
|
|
318
|
+
beforeAll(() => __resetRegistryForTesting());
|
|
319
|
+
|
|
317
320
|
// The voice-duplex-handoff flag is toggled via the override cache in a few
|
|
318
321
|
// tests below; reset it so it never leaks into the flag-off default cases.
|
|
319
322
|
afterEach(() => clearCachedOverrides());
|
|
@@ -1325,8 +1328,13 @@ describe("LiveVoiceSession server VAD", () => {
|
|
|
1325
1328
|
await waitFor(() => calls.some((c) => c.content === "second question"));
|
|
1326
1329
|
// Barge #2 is a rapid second interruption: it bumps the sequence
|
|
1327
1330
|
// synchronously, so A is invalidated even before barge #2's own async detach
|
|
1328
|
-
// runs
|
|
1331
|
+
// runs — and A's run itself is ABORTED, so a still-writing older
|
|
1332
|
+
// continuation can never overlap the newer full-ability one.
|
|
1329
1333
|
await session.handleBinaryAudio(SUSTAINED_LOUD_CHUNK);
|
|
1334
|
+
await waitFor(
|
|
1335
|
+
() =>
|
|
1336
|
+
spawnBackgroundContinuation.mock.calls[0]?.[0]?.signal.aborted === true,
|
|
1337
|
+
);
|
|
1330
1338
|
// A finishes AFTER barge #2 and must be rejected. The newer continuation is
|
|
1331
1339
|
// left pending so it can't mask the bug by superseding A itself.
|
|
1332
1340
|
resolvers[0]?.("OLDER_RESULT");
|
|
@@ -1342,8 +1350,12 @@ describe("LiveVoiceSession server VAD", () => {
|
|
|
1342
1350
|
"background you finished",
|
|
1343
1351
|
);
|
|
1344
1352
|
|
|
1345
|
-
//
|
|
1353
|
+
// The newer continuation was not collateral damage of superseding A.
|
|
1346
1354
|
await waitFor(() => resolvers.length === 2);
|
|
1355
|
+
expect(spawnBackgroundContinuation.mock.calls[1]?.[0]?.signal.aborted).toBe(
|
|
1356
|
+
false,
|
|
1357
|
+
);
|
|
1358
|
+
// Cleanup the still-pending newer continuation.
|
|
1347
1359
|
resolvers[1]?.("");
|
|
1348
1360
|
});
|
|
1349
1361
|
|
|
@@ -1415,6 +1427,198 @@ describe("LiveVoiceSession server VAD", () => {
|
|
|
1415
1427
|
resolvers[1]?.("");
|
|
1416
1428
|
});
|
|
1417
1429
|
|
|
1430
|
+
test("voice-duplex-handoff on: a foreground consequential tool start aborts an in-flight continuation", async () => {
|
|
1431
|
+
setCachedOverrides({ "voice-duplex-handoff": true }, { fromGateway: true });
|
|
1432
|
+
// Hang the continuation so it is still running when the follow-up turn
|
|
1433
|
+
// starts mutating; resolve it when its signal aborts.
|
|
1434
|
+
const spawnBackgroundContinuation = mock(
|
|
1435
|
+
(args: {
|
|
1436
|
+
parentConversationId: string;
|
|
1437
|
+
objective: string;
|
|
1438
|
+
label: string;
|
|
1439
|
+
signal: AbortSignal;
|
|
1440
|
+
}): Promise<string> =>
|
|
1441
|
+
new Promise<string>((resolve) => {
|
|
1442
|
+
args.signal.addEventListener("abort", () => resolve(""), {
|
|
1443
|
+
once: true,
|
|
1444
|
+
});
|
|
1445
|
+
}),
|
|
1446
|
+
);
|
|
1447
|
+
const calls: VoiceTurnOptions[] = [];
|
|
1448
|
+
const startVoiceTurn = mock(async (options: VoiceTurnOptions) => {
|
|
1449
|
+
calls.push(options);
|
|
1450
|
+
return { turnId: `bridge-turn-${calls.length}`, abort: mock() };
|
|
1451
|
+
});
|
|
1452
|
+
const streamTtsAudio = mock(async (options: LiveVoiceTtsOptions) => {
|
|
1453
|
+
options.onAudioChunk(makeTtsChunk("assistant audio"));
|
|
1454
|
+
return makeTtsResult("assistant audio");
|
|
1455
|
+
});
|
|
1456
|
+
const { frames, session } = createHarness({
|
|
1457
|
+
finals: ["first question", "second question"],
|
|
1458
|
+
startVoiceTurn,
|
|
1459
|
+
streamTtsAudio,
|
|
1460
|
+
spawnBackgroundContinuation,
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
await session.start();
|
|
1464
|
+
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
1465
|
+
await waitFor(() => frames.some((frame) => frame.type === "thinking"));
|
|
1466
|
+
|
|
1467
|
+
// Barge in during thinking: the continuation detaches, the follow-up turn
|
|
1468
|
+
// launches while it is still in flight.
|
|
1469
|
+
await session.handleBinaryAudio(SUSTAINED_LOUD_CHUNK);
|
|
1470
|
+
await waitFor(() => spawnBackgroundContinuation.mock.calls.length === 1);
|
|
1471
|
+
await waitFor(() => calls.some((c) => c.content === "second question"));
|
|
1472
|
+
|
|
1473
|
+
const signal = spawnBackgroundContinuation.mock.calls[0]?.[0]?.signal;
|
|
1474
|
+
expect(signal?.aborted).toBe(false);
|
|
1475
|
+
|
|
1476
|
+
// The follow-up turn starts a consequential tool: foreground wins the
|
|
1477
|
+
// workspace, so the still-running continuation is aborted before the two
|
|
1478
|
+
// can race on writes. `remember` is deliberately NOT on the core
|
|
1479
|
+
// side-effect name list — the classification must fail closed on any
|
|
1480
|
+
// tool that is not provably read-only, not just named writers.
|
|
1481
|
+
const followUp = calls.find((c) => c.content === "second question");
|
|
1482
|
+
followUp?.callbacks?.tool_use_start?.("remember", {
|
|
1483
|
+
toolUseId: "tool-1",
|
|
1484
|
+
});
|
|
1485
|
+
expect(signal?.aborted).toBe(true);
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
test("voice-duplex-handoff on: a read-only built-in leaves the continuation running; an unclassified tool fails closed", async () => {
|
|
1489
|
+
setCachedOverrides({ "voice-duplex-handoff": true }, { fromGateway: true });
|
|
1490
|
+
let abortListenerArmed = false;
|
|
1491
|
+
const spawnBackgroundContinuation = mock(
|
|
1492
|
+
(args: {
|
|
1493
|
+
parentConversationId: string;
|
|
1494
|
+
objective: string;
|
|
1495
|
+
label: string;
|
|
1496
|
+
signal: AbortSignal;
|
|
1497
|
+
}): Promise<string> =>
|
|
1498
|
+
new Promise<string>((resolve) => {
|
|
1499
|
+
abortListenerArmed = true;
|
|
1500
|
+
args.signal.addEventListener("abort", () => resolve(""), {
|
|
1501
|
+
once: true,
|
|
1502
|
+
});
|
|
1503
|
+
}),
|
|
1504
|
+
);
|
|
1505
|
+
const calls: VoiceTurnOptions[] = [];
|
|
1506
|
+
const startVoiceTurn = mock(async (options: VoiceTurnOptions) => {
|
|
1507
|
+
calls.push(options);
|
|
1508
|
+
return { turnId: `bridge-turn-${calls.length}`, abort: mock() };
|
|
1509
|
+
});
|
|
1510
|
+
const streamTtsAudio = mock(async (options: LiveVoiceTtsOptions) => {
|
|
1511
|
+
options.onAudioChunk(makeTtsChunk("assistant audio"));
|
|
1512
|
+
return makeTtsResult("assistant audio");
|
|
1513
|
+
});
|
|
1514
|
+
const { frames, session } = createHarness({
|
|
1515
|
+
finals: ["first question", "second question"],
|
|
1516
|
+
startVoiceTurn,
|
|
1517
|
+
streamTtsAudio,
|
|
1518
|
+
spawnBackgroundContinuation,
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
await session.start();
|
|
1522
|
+
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
1523
|
+
await waitFor(() => frames.some((frame) => frame.type === "thinking"));
|
|
1524
|
+
await session.handleBinaryAudio(SUSTAINED_LOUD_CHUNK);
|
|
1525
|
+
await waitFor(() => abortListenerArmed);
|
|
1526
|
+
await waitFor(() => calls.some((c) => c.content === "second question"));
|
|
1527
|
+
|
|
1528
|
+
// A provably read-only built-in cannot contend on the workspace: the
|
|
1529
|
+
// continuation survives (this is the topic-change case the handoff
|
|
1530
|
+
// exists for).
|
|
1531
|
+
const followUp = calls.find((c) => c.content === "second question");
|
|
1532
|
+
followUp?.callbacks?.tool_use_start?.("file_read", { toolUseId: "tool-1" });
|
|
1533
|
+
const signal = spawnBackgroundContinuation.mock.calls[0]?.[0]?.signal;
|
|
1534
|
+
expect(signal?.aborted).toBe(false);
|
|
1535
|
+
|
|
1536
|
+
// skill_load is non-contending too: it only registers tool definitions,
|
|
1537
|
+
// and it is the first call of a follow-up turn that re-enters the skill
|
|
1538
|
+
// the interrupted turn was using — it must not kill the continuation.
|
|
1539
|
+
followUp?.callbacks?.tool_use_start?.("skill_load", {
|
|
1540
|
+
toolUseId: "tool-2",
|
|
1541
|
+
});
|
|
1542
|
+
expect(signal?.aborted).toBe(false);
|
|
1543
|
+
|
|
1544
|
+
// web_fetch is a network read: it cannot corrupt local state, so it does
|
|
1545
|
+
// not contend even though it is on the core SIDE_EFFECT_TOOLS list (which
|
|
1546
|
+
// exists for a permission question, not a contention one). This is the
|
|
1547
|
+
// canonical topic-change case — "what's the weather?" over a running
|
|
1548
|
+
// build — and it is exactly what the handoff exists to keep alive.
|
|
1549
|
+
followUp?.callbacks?.tool_use_start?.("web_fetch", {
|
|
1550
|
+
toolUseId: "tool-3",
|
|
1551
|
+
});
|
|
1552
|
+
expect(signal?.aborted).toBe(false);
|
|
1553
|
+
|
|
1554
|
+
// A tool the registry does not know (e.g. an MCP or plugin tool) is not
|
|
1555
|
+
// provably non-contending: fail closed and abort.
|
|
1556
|
+
followUp?.callbacks?.tool_use_start?.("calendar_lookup", {
|
|
1557
|
+
toolUseId: "tool-4",
|
|
1558
|
+
});
|
|
1559
|
+
expect(signal?.aborted).toBe(true);
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
test("voice-duplex-handoff on: a foreground-wins abort keeps an already-stashed continuation result", async () => {
|
|
1563
|
+
setCachedOverrides({ "voice-duplex-handoff": true }, { fromGateway: true });
|
|
1564
|
+
let resolveContinuation: ((result: string) => void) | undefined;
|
|
1565
|
+
const spawnBackgroundContinuation = mock(
|
|
1566
|
+
(_args: {
|
|
1567
|
+
parentConversationId: string;
|
|
1568
|
+
objective: string;
|
|
1569
|
+
label: string;
|
|
1570
|
+
signal: AbortSignal;
|
|
1571
|
+
}): Promise<string> =>
|
|
1572
|
+
new Promise<string>((resolve) => {
|
|
1573
|
+
resolveContinuation = resolve;
|
|
1574
|
+
}),
|
|
1575
|
+
);
|
|
1576
|
+
const calls: VoiceTurnOptions[] = [];
|
|
1577
|
+
const startVoiceTurn = mock(async (options: VoiceTurnOptions) => {
|
|
1578
|
+
calls.push(options);
|
|
1579
|
+
return { turnId: `bridge-turn-${calls.length}`, abort: mock() };
|
|
1580
|
+
});
|
|
1581
|
+
const streamTtsAudio = mock(async (options: LiveVoiceTtsOptions) => {
|
|
1582
|
+
options.onAudioChunk(makeTtsChunk("assistant audio"));
|
|
1583
|
+
return makeTtsResult("assistant audio");
|
|
1584
|
+
});
|
|
1585
|
+
const { frames, session } = createHarness({
|
|
1586
|
+
finals: ["first question", "second question", "third question"],
|
|
1587
|
+
startVoiceTurn,
|
|
1588
|
+
streamTtsAudio,
|
|
1589
|
+
spawnBackgroundContinuation,
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
await session.start();
|
|
1593
|
+
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
1594
|
+
await waitFor(() => frames.some((frame) => frame.type === "thinking"));
|
|
1595
|
+
|
|
1596
|
+
// Barge in; the follow-up turn launches, then the continuation finishes
|
|
1597
|
+
// and stashes its answer for the NEXT turn while the follow-up still runs.
|
|
1598
|
+
await session.handleBinaryAudio(SUSTAINED_LOUD_CHUNK);
|
|
1599
|
+
await waitFor(() => resolveContinuation !== undefined);
|
|
1600
|
+
await waitFor(() => calls.some((c) => c.content === "second question"));
|
|
1601
|
+
resolveContinuation?.("THE_RESULT");
|
|
1602
|
+
await flushAsyncCallbacks();
|
|
1603
|
+
|
|
1604
|
+
// The follow-up turn starts writing: the foreground-wins abort fires, but
|
|
1605
|
+
// a completed continuation's stashed answer cannot race anything and is
|
|
1606
|
+
// kept.
|
|
1607
|
+
const followUp = calls.find((c) => c.content === "second question");
|
|
1608
|
+
followUp?.callbacks?.tool_use_start?.("file_write", {
|
|
1609
|
+
toolUseId: "tool-1",
|
|
1610
|
+
});
|
|
1611
|
+
followUp?.callbacks?.assistant_text_delta?.(makeTextDelta("ok"));
|
|
1612
|
+
followUp?.callbacks?.message_complete?.(makeMessageComplete());
|
|
1613
|
+
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
1614
|
+
|
|
1615
|
+
// The next turn still folds the stashed answer in as context.
|
|
1616
|
+
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
1617
|
+
await waitFor(() => calls.some((c) => c.content === "third question"));
|
|
1618
|
+
const resurfaced = calls.find((c) => c.content === "third question");
|
|
1619
|
+
expect(resurfaced?.voiceControlPrompt).toContain("THE_RESULT");
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1418
1622
|
test("a late assistant_text_delta after a thinking barge-in never reaches the client", async () => {
|
|
1419
1623
|
let callbacks: VoiceTurnCallbacks | undefined;
|
|
1420
1624
|
const abort = mock();
|
|
@@ -2691,395 +2895,7 @@ describe("LiveVoiceSession sustained-speech barge-in guard", () => {
|
|
|
2691
2895
|
});
|
|
2692
2896
|
});
|
|
2693
2897
|
|
|
2694
|
-
// Stub semantic-endpointing decider: scripted hold/release decisions consumed
|
|
2695
|
-
// in order, with every consulted input captured. Falls back to "release" once
|
|
2696
|
-
// the script is exhausted, mirroring the real decider's fail-open bias.
|
|
2697
|
-
function makeFrontDecider(decisions: Array<"hold" | "release">): {
|
|
2698
|
-
decider: VoiceFrontDecider;
|
|
2699
|
-
calls: VoiceEndpointDecisionInput[];
|
|
2700
|
-
} {
|
|
2701
|
-
const calls: VoiceEndpointDecisionInput[] = [];
|
|
2702
|
-
return {
|
|
2703
|
-
calls,
|
|
2704
|
-
decider: {
|
|
2705
|
-
decideEndpoint: async (input) => {
|
|
2706
|
-
calls.push(input);
|
|
2707
|
-
return { action: decisions[calls.length - 1] ?? "release" };
|
|
2708
|
-
},
|
|
2709
|
-
generateAckText: async () => null,
|
|
2710
|
-
generateProgressText: async () => null,
|
|
2711
|
-
},
|
|
2712
|
-
};
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
describe("LiveVoiceSession semantic endpointing", () => {
|
|
2716
|
-
// Arms the harness session, waits for its transcriber, and seeds a partial
|
|
2717
|
-
// so the silence boundary has transcript text for the decider to judge.
|
|
2718
|
-
async function startWithPartial(
|
|
2719
|
-
session: LiveVoiceSession,
|
|
2720
|
-
transcribers: MockStreamingTranscriber[],
|
|
2721
|
-
partialText = "hello wor",
|
|
2722
|
-
): Promise<void> {
|
|
2723
|
-
await session.start();
|
|
2724
|
-
await waitFor(() => transcribers.length === 1);
|
|
2725
|
-
// Let the transcriber's start() wiring settle before emitting events.
|
|
2726
|
-
await flushAsyncCallbacks();
|
|
2727
|
-
transcribers[0]?.emit({ type: "partial", text: partialText });
|
|
2728
|
-
}
|
|
2729
|
-
|
|
2730
|
-
test("a held silence boundary sends no utterance_end and replays after the extension", async () => {
|
|
2731
|
-
const { decider, calls } = makeFrontDecider(["hold", "release"]);
|
|
2732
|
-
const turnStarter = makeAutoCompletingTurnStarter(["Hi there."]);
|
|
2733
|
-
const { frames, session, transcribers } = createHarness({
|
|
2734
|
-
finals: ["hello world"],
|
|
2735
|
-
startVoiceTurn: turnStarter.startVoiceTurn,
|
|
2736
|
-
frontDecider: decider,
|
|
2737
|
-
frontModelConfig: { endpointExtensionMs: 30 },
|
|
2738
|
-
});
|
|
2739
|
-
|
|
2740
|
-
await startWithPartial(session, transcribers);
|
|
2741
|
-
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
2742
|
-
|
|
2743
|
-
// First silence boundary: the decider holds — no utterance_end, no turn.
|
|
2744
|
-
await waitFor(() => calls.length === 1);
|
|
2745
|
-
expect(calls[0]).toEqual({
|
|
2746
|
-
transcriptSoFar: "",
|
|
2747
|
-
latestPartial: "hello wor",
|
|
2748
|
-
silenceThresholdMs: 40,
|
|
2749
|
-
extensionCount: 0,
|
|
2750
|
-
});
|
|
2751
|
-
expect(countType(frames, "utterance_end")).toBe(0);
|
|
2752
|
-
expect(countType(frames, "thinking")).toBe(0);
|
|
2753
|
-
|
|
2754
|
-
// The extension elapses in continued silence: the boundary replays, the
|
|
2755
|
-
// decider releases, and the turn launches normally.
|
|
2756
|
-
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
2757
|
-
expect(calls).toHaveLength(2);
|
|
2758
|
-
expect(calls[1]).toMatchObject({ extensionCount: 1 });
|
|
2759
|
-
expect(frameTypes(frames)).toEqual([
|
|
2760
|
-
"ready",
|
|
2761
|
-
"stt_partial",
|
|
2762
|
-
"speech_started",
|
|
2763
|
-
"utterance_end",
|
|
2764
|
-
"stt_final",
|
|
2765
|
-
"thinking",
|
|
2766
|
-
"assistant_text_delta",
|
|
2767
|
-
"tts_done",
|
|
2768
|
-
]);
|
|
2769
|
-
expect(
|
|
2770
|
-
frames.find((frame) => frame.type === "utterance_end"),
|
|
2771
|
-
).toMatchObject({ reason: "silence" });
|
|
2772
|
-
expect(turnStarter.calls[0]).toMatchObject({ content: "hello world" });
|
|
2773
|
-
});
|
|
2774
|
-
|
|
2775
|
-
test("ptt_release during a held pause still emits utterance_end", async () => {
|
|
2776
|
-
const { decider, calls } = makeFrontDecider(["hold"]);
|
|
2777
|
-
const turnStarter = makeAutoCompletingTurnStarter(["Hi."]);
|
|
2778
|
-
const { frames, session, transcribers } = createHarness({
|
|
2779
|
-
finals: ["hello world"],
|
|
2780
|
-
startVoiceTurn: turnStarter.startVoiceTurn,
|
|
2781
|
-
frontDecider: decider,
|
|
2782
|
-
// Long extension so the pending replay cannot race the manual release.
|
|
2783
|
-
frontModelConfig: { endpointExtensionMs: 5_000 },
|
|
2784
|
-
});
|
|
2785
|
-
|
|
2786
|
-
await startWithPartial(session, transcribers);
|
|
2787
|
-
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
2788
|
-
await waitFor(() => calls.length === 1);
|
|
2789
|
-
expect(countType(frames, "utterance_end")).toBe(0);
|
|
2790
|
-
|
|
2791
|
-
// The detector's turn already ended (the hold suppressed its boundary),
|
|
2792
|
-
// so this release takes the no-active-detector path — the client must
|
|
2793
|
-
// still receive the utterance_end frame it uses to leave `listening`.
|
|
2794
|
-
await session.handleClientFrame({ type: "ptt_release" });
|
|
2795
|
-
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
2796
|
-
expect(countType(frames, "utterance_end")).toBe(1);
|
|
2797
|
-
// Manual release never re-consults the decider.
|
|
2798
|
-
expect(calls).toHaveLength(1);
|
|
2799
|
-
expect(turnStarter.calls[0]).toMatchObject({ content: "hello world" });
|
|
2800
|
-
});
|
|
2801
|
-
|
|
2802
|
-
test("speech resuming during a hold cancels the replay and the utterance keeps accumulating", async () => {
|
|
2803
|
-
const { decider, calls } = makeFrontDecider(["hold", "release"]);
|
|
2804
|
-
const turnStarter = makeAutoCompletingTurnStarter(["Okay."]);
|
|
2805
|
-
const { frames, session, transcribers } = createHarness({
|
|
2806
|
-
finals: ["hello there world"],
|
|
2807
|
-
startVoiceTurn: turnStarter.startVoiceTurn,
|
|
2808
|
-
frontDecider: decider,
|
|
2809
|
-
// Comfortably longer than the resumed speech's own silence boundary,
|
|
2810
|
-
// so a leaked (uncancelled) replay would surface as a third decider
|
|
2811
|
-
// consult / second utterance_end below.
|
|
2812
|
-
frontModelConfig: { endpointExtensionMs: 200 },
|
|
2813
|
-
});
|
|
2814
|
-
|
|
2815
|
-
await startWithPartial(session, transcribers, "hello");
|
|
2816
|
-
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
2817
|
-
await waitFor(() => calls.length === 1);
|
|
2818
|
-
expect(countType(frames, "utterance_end")).toBe(0);
|
|
2819
|
-
|
|
2820
|
-
// Speech resumes during the hold; the fresh silence boundary after it —
|
|
2821
|
-
// not the cancelled replay timer — drives the next decision.
|
|
2822
|
-
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
2823
|
-
await waitFor(() => calls.length === 2);
|
|
2824
|
-
expect(calls[1]).toMatchObject({ extensionCount: 1 });
|
|
2825
|
-
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
2826
|
-
|
|
2827
|
-
// Wait out the original extension window: the cancelled replay never
|
|
2828
|
-
// re-fires the boundary.
|
|
2829
|
-
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
2830
|
-
expect(calls).toHaveLength(2);
|
|
2831
|
-
expect(countType(frames, "utterance_end")).toBe(1);
|
|
2832
|
-
// Both speech bursts fed the same utterance's transcriber (the second
|
|
2833
|
-
// resolves only when the completed turn re-arms), producing one released
|
|
2834
|
-
// turn end-to-end.
|
|
2835
|
-
expect(transcribers[0]?.received.length).toBeGreaterThanOrEqual(2);
|
|
2836
|
-
expect(turnStarter.calls).toHaveLength(1);
|
|
2837
|
-
expect(turnStarter.calls[0]).toMatchObject({
|
|
2838
|
-
content: "hello there world",
|
|
2839
|
-
});
|
|
2840
|
-
});
|
|
2841
|
-
|
|
2842
|
-
test("a release decision produces the same frame sequence as the flag-off path", async () => {
|
|
2843
|
-
const { decider, calls } = makeFrontDecider(["release"]);
|
|
2844
|
-
const turnStarter = makeAutoCompletingTurnStarter(["Hi there."]);
|
|
2845
|
-
const { frames, session, transcribers } = createHarness({
|
|
2846
|
-
finals: ["hello world"],
|
|
2847
|
-
startVoiceTurn: turnStarter.startVoiceTurn,
|
|
2848
|
-
frontDecider: decider,
|
|
2849
|
-
});
|
|
2850
|
-
|
|
2851
|
-
await startWithPartial(session, transcribers);
|
|
2852
|
-
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
2853
|
-
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
2854
|
-
|
|
2855
|
-
expect(calls).toHaveLength(1);
|
|
2856
|
-
expect(frameTypes(frames)).toEqual([
|
|
2857
|
-
"ready",
|
|
2858
|
-
"stt_partial",
|
|
2859
|
-
"speech_started",
|
|
2860
|
-
"utterance_end",
|
|
2861
|
-
"stt_final",
|
|
2862
|
-
"thinking",
|
|
2863
|
-
"assistant_text_delta",
|
|
2864
|
-
"tts_done",
|
|
2865
|
-
]);
|
|
2866
|
-
expect(
|
|
2867
|
-
frames.find((frame) => frame.type === "utterance_end"),
|
|
2868
|
-
).toMatchObject({ reason: "silence" });
|
|
2869
|
-
});
|
|
2870
|
-
|
|
2871
|
-
test("a rejecting decider still releases the boundary with the normal frame sequence", async () => {
|
|
2872
|
-
// The decider contract never rejects, but a buggy injected stub (or a
|
|
2873
|
-
// future regression) must not silently drop the silence boundary: the
|
|
2874
|
-
// session's belt catches the rejection and releases.
|
|
2875
|
-
const calls: VoiceEndpointDecisionInput[] = [];
|
|
2876
|
-
const decider: VoiceFrontDecider = {
|
|
2877
|
-
decideEndpoint: async (input) => {
|
|
2878
|
-
calls.push(input);
|
|
2879
|
-
throw new Error("stub decider boom");
|
|
2880
|
-
},
|
|
2881
|
-
generateAckText: async () => null,
|
|
2882
|
-
generateProgressText: async () => null,
|
|
2883
|
-
};
|
|
2884
|
-
const turnStarter = makeAutoCompletingTurnStarter(["Hi there."]);
|
|
2885
|
-
const { frames, session, transcribers } = createHarness({
|
|
2886
|
-
finals: ["hello world"],
|
|
2887
|
-
startVoiceTurn: turnStarter.startVoiceTurn,
|
|
2888
|
-
frontDecider: decider,
|
|
2889
|
-
});
|
|
2890
|
-
|
|
2891
|
-
await startWithPartial(session, transcribers);
|
|
2892
|
-
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
2893
|
-
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
2894
|
-
|
|
2895
|
-
expect(calls).toHaveLength(1);
|
|
2896
|
-
expect(frameTypes(frames)).toEqual([
|
|
2897
|
-
"ready",
|
|
2898
|
-
"stt_partial",
|
|
2899
|
-
"speech_started",
|
|
2900
|
-
"utterance_end",
|
|
2901
|
-
"stt_final",
|
|
2902
|
-
"thinking",
|
|
2903
|
-
"assistant_text_delta",
|
|
2904
|
-
"tts_done",
|
|
2905
|
-
]);
|
|
2906
|
-
expect(
|
|
2907
|
-
frames.find((frame) => frame.type === "utterance_end"),
|
|
2908
|
-
).toMatchObject({ reason: "silence" });
|
|
2909
|
-
expect(turnStarter.calls[0]).toMatchObject({ content: "hello world" });
|
|
2910
|
-
});
|
|
2911
|
-
|
|
2912
|
-
test("a slow decider adds only its own bounded delay and then releases normally", async () => {
|
|
2913
|
-
// Resolves "release" only after a delay comfortably longer than every
|
|
2914
|
-
// other timer in the flow — the boundary must wait it out and then run
|
|
2915
|
-
// the unchanged release sequence.
|
|
2916
|
-
const calls: VoiceEndpointDecisionInput[] = [];
|
|
2917
|
-
const decider: VoiceFrontDecider = {
|
|
2918
|
-
decideEndpoint: async (input) => {
|
|
2919
|
-
calls.push(input);
|
|
2920
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
2921
|
-
return { action: "release" };
|
|
2922
|
-
},
|
|
2923
|
-
generateAckText: async () => null,
|
|
2924
|
-
generateProgressText: async () => null,
|
|
2925
|
-
};
|
|
2926
|
-
const turnStarter = makeAutoCompletingTurnStarter(["Hi there."]);
|
|
2927
|
-
const { frames, session, transcribers } = createHarness({
|
|
2928
|
-
finals: ["hello world"],
|
|
2929
|
-
startVoiceTurn: turnStarter.startVoiceTurn,
|
|
2930
|
-
frontDecider: decider,
|
|
2931
|
-
});
|
|
2932
|
-
|
|
2933
|
-
await startWithPartial(session, transcribers);
|
|
2934
|
-
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
2935
|
-
|
|
2936
|
-
// While the decision is pending, the boundary stays unreleased.
|
|
2937
|
-
await waitFor(() => calls.length === 1);
|
|
2938
|
-
expect(countType(frames, "utterance_end")).toBe(0);
|
|
2939
|
-
expect(countType(frames, "thinking")).toBe(0);
|
|
2940
|
-
|
|
2941
|
-
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
2942
|
-
expect(calls).toHaveLength(1);
|
|
2943
|
-
expect(frameTypes(frames)).toEqual([
|
|
2944
|
-
"ready",
|
|
2945
|
-
"stt_partial",
|
|
2946
|
-
"speech_started",
|
|
2947
|
-
"utterance_end",
|
|
2948
|
-
"stt_final",
|
|
2949
|
-
"thinking",
|
|
2950
|
-
"assistant_text_delta",
|
|
2951
|
-
"tts_done",
|
|
2952
|
-
]);
|
|
2953
|
-
expect(
|
|
2954
|
-
frames.find((frame) => frame.type === "utterance_end"),
|
|
2955
|
-
).toMatchObject({ reason: "silence" });
|
|
2956
|
-
expect(turnStarter.calls[0]).toMatchObject({ content: "hello world" });
|
|
2957
|
-
});
|
|
2958
|
-
|
|
2959
|
-
test("endpointMaxExtensions caps consecutive holds and forces the release", async () => {
|
|
2960
|
-
const { decider, calls } = makeFrontDecider(["hold", "hold", "hold"]);
|
|
2961
|
-
const turnStarter = makeAutoCompletingTurnStarter(["Hi there."]);
|
|
2962
|
-
const { frames, session, transcribers } = createHarness({
|
|
2963
|
-
finals: ["hello world"],
|
|
2964
|
-
startVoiceTurn: turnStarter.startVoiceTurn,
|
|
2965
|
-
frontDecider: decider,
|
|
2966
|
-
frontModelConfig: { endpointExtensionMs: 20, endpointMaxExtensions: 1 },
|
|
2967
|
-
});
|
|
2968
|
-
|
|
2969
|
-
await startWithPartial(session, transcribers);
|
|
2970
|
-
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
2971
|
-
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
2972
|
-
|
|
2973
|
-
// The single allowed hold consumed the decider's one consult; the replay
|
|
2974
|
-
// hit the cap and released without asking again.
|
|
2975
|
-
expect(calls).toHaveLength(1);
|
|
2976
|
-
expect(countType(frames, "utterance_end")).toBe(1);
|
|
2977
|
-
expect(turnStarter.calls).toHaveLength(1);
|
|
2978
|
-
expect(turnStarter.calls[0]).toMatchObject({ content: "hello world" });
|
|
2979
|
-
});
|
|
2980
|
-
|
|
2981
|
-
test("a max-duration boundary bypasses the decider entirely", async () => {
|
|
2982
|
-
const { decider, calls } = makeFrontDecider(["hold"]);
|
|
2983
|
-
const turnStarter = makeAutoCompletingTurnStarter(["Hi there."]);
|
|
2984
|
-
const { frames, session, transcribers } = createHarness({
|
|
2985
|
-
finals: ["hello world"],
|
|
2986
|
-
startVoiceTurn: turnStarter.startVoiceTurn,
|
|
2987
|
-
frontDecider: decider,
|
|
2988
|
-
// Silence can never fire; the max-duration cap ends the turn instead.
|
|
2989
|
-
turnDetectorConfig: { silenceThresholdMs: 10_000, maxTurnDurationMs: 40 },
|
|
2990
|
-
});
|
|
2991
|
-
|
|
2992
|
-
await startWithPartial(session, transcribers);
|
|
2993
|
-
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
2994
|
-
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
2995
|
-
|
|
2996
|
-
expect(calls).toHaveLength(0);
|
|
2997
|
-
expect(
|
|
2998
|
-
frames.find((frame) => frame.type === "utterance_end"),
|
|
2999
|
-
).toMatchObject({ reason: "max-duration" });
|
|
3000
|
-
expect(turnStarter.calls).toHaveLength(1);
|
|
3001
|
-
});
|
|
3002
|
-
|
|
3003
|
-
test("a ptt_release forced boundary bypasses the decider and releases immediately", async () => {
|
|
3004
|
-
// A hold-happy decider: if the manual release were routed through it,
|
|
3005
|
-
// the boundary would be deferred and the frame sequence below would gain
|
|
3006
|
-
// an extension delay (or stall entirely).
|
|
3007
|
-
const { decider, calls } = makeFrontDecider(["hold", "hold"]);
|
|
3008
|
-
const turnStarter = makeAutoCompletingTurnStarter(["Hi there."]);
|
|
3009
|
-
const { frames, session, transcribers } = createHarness({
|
|
3010
|
-
finals: ["hello world"],
|
|
3011
|
-
startVoiceTurn: turnStarter.startVoiceTurn,
|
|
3012
|
-
frontDecider: decider,
|
|
3013
|
-
// A genuine silence boundary can never fire during the test; only the
|
|
3014
|
-
// client's explicit release ends the turn.
|
|
3015
|
-
turnDetectorConfig: { silenceThresholdMs: 10_000 },
|
|
3016
|
-
});
|
|
3017
|
-
|
|
3018
|
-
await startWithPartial(session, transcribers);
|
|
3019
|
-
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
3020
|
-
await waitFor(() =>
|
|
3021
|
-
frames.some((frame) => frame.type === "speech_started"),
|
|
3022
|
-
);
|
|
3023
|
-
|
|
3024
|
-
await session.handleClientFrame({ type: "ptt_release" });
|
|
3025
|
-
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
3026
|
-
|
|
3027
|
-
// The explicit release was never second-guessed: no decider consult, and
|
|
3028
|
-
// the boundary produced the normal utterance_end → turn-launch sequence.
|
|
3029
|
-
expect(calls).toHaveLength(0);
|
|
3030
|
-
expect(frameTypes(frames)).toEqual([
|
|
3031
|
-
"ready",
|
|
3032
|
-
"stt_partial",
|
|
3033
|
-
"speech_started",
|
|
3034
|
-
"utterance_end",
|
|
3035
|
-
"stt_final",
|
|
3036
|
-
"thinking",
|
|
3037
|
-
"assistant_text_delta",
|
|
3038
|
-
"tts_done",
|
|
3039
|
-
]);
|
|
3040
|
-
expect(
|
|
3041
|
-
frames.find((frame) => frame.type === "utterance_end"),
|
|
3042
|
-
).toMatchObject({ reason: "silence" });
|
|
3043
|
-
expect(turnStarter.calls).toHaveLength(1);
|
|
3044
|
-
expect(turnStarter.calls[0]).toMatchObject({ content: "hello world" });
|
|
3045
|
-
});
|
|
3046
|
-
|
|
3047
|
-
test("session close during a hold clears the extension timer", async () => {
|
|
3048
|
-
const { decider, calls } = makeFrontDecider(["hold", "hold"]);
|
|
3049
|
-
const { frames, session, transcribers } = createHarness({
|
|
3050
|
-
finals: ["hello world"],
|
|
3051
|
-
frontDecider: decider,
|
|
3052
|
-
frontModelConfig: { endpointExtensionMs: 30 },
|
|
3053
|
-
});
|
|
3054
|
-
|
|
3055
|
-
await startWithPartial(session, transcribers);
|
|
3056
|
-
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
3057
|
-
await waitFor(() => calls.length === 1);
|
|
3058
|
-
expect(countType(frames, "utterance_end")).toBe(0);
|
|
3059
|
-
|
|
3060
|
-
await session.close("client_end");
|
|
3061
|
-
const framesAtClose = frames.length;
|
|
3062
|
-
|
|
3063
|
-
// Well past the extension window: the cleared timer never replays the
|
|
3064
|
-
// boundary — no utterance_end, no second decider consult, no new frames.
|
|
3065
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
3066
|
-
expect(calls).toHaveLength(1);
|
|
3067
|
-
expect(countType(frames, "utterance_end")).toBe(0);
|
|
3068
|
-
expect(frames.length).toBe(framesAtClose);
|
|
3069
|
-
});
|
|
3070
|
-
});
|
|
3071
|
-
|
|
3072
2898
|
describe("LiveVoiceSession unified front-door endpointing", () => {
|
|
3073
|
-
const enableUnifiedFlags = () =>
|
|
3074
|
-
setCachedOverrides(
|
|
3075
|
-
{
|
|
3076
|
-
"voice-mode": true,
|
|
3077
|
-
},
|
|
3078
|
-
{ fromGateway: true },
|
|
3079
|
-
);
|
|
3080
|
-
|
|
3081
|
-
afterEach(() => clearCachedOverrides());
|
|
3082
|
-
|
|
3083
2899
|
function spokenDeltaText(frames: LiveVoiceServerFrame[]): string {
|
|
3084
2900
|
return frames
|
|
3085
2901
|
.filter((frame) => frame.type === "assistant_text_delta")
|
|
@@ -3114,23 +2930,12 @@ describe("LiveVoiceSession unified front-door endpointing", () => {
|
|
|
3114
2930
|
return { startVoiceTurn, calls, discard };
|
|
3115
2931
|
}
|
|
3116
2932
|
|
|
3117
|
-
// A decider that
|
|
3118
|
-
//
|
|
3119
|
-
function
|
|
3120
|
-
decider: VoiceFrontDecider;
|
|
3121
|
-
endpointCalls: () => number;
|
|
3122
|
-
} {
|
|
3123
|
-
let count = 0;
|
|
2933
|
+
// A decider that phrases nothing: the front-door leg IS the endpoint
|
|
2934
|
+
// decision, so these tests only need the decider seam to exist.
|
|
2935
|
+
function makeSilentDecider(): VoiceFrontDecider {
|
|
3124
2936
|
return {
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
count += 1;
|
|
3128
|
-
return { action: "release" as const };
|
|
3129
|
-
},
|
|
3130
|
-
generateAckText: async () => null,
|
|
3131
|
-
generateProgressText: async () => null,
|
|
3132
|
-
},
|
|
3133
|
-
endpointCalls: () => count,
|
|
2937
|
+
generateAckText: async () => null,
|
|
2938
|
+
generateProgressText: async () => null,
|
|
3134
2939
|
};
|
|
3135
2940
|
}
|
|
3136
2941
|
|
|
@@ -3146,8 +2951,7 @@ describe("LiveVoiceSession unified front-door endpointing", () => {
|
|
|
3146
2951
|
}
|
|
3147
2952
|
|
|
3148
2953
|
test("a chatty answer commits: verdict leg replaces the decider, frames follow commit order", async () => {
|
|
3149
|
-
|
|
3150
|
-
const { decider, endpointCalls } = makeUntouchableDecider();
|
|
2954
|
+
const decider = makeSilentDecider();
|
|
3151
2955
|
const starter = makeVerdictTurnStarter([["Hey! Not much."]]);
|
|
3152
2956
|
const { frames, session, transcribers } = createHarness({
|
|
3153
2957
|
finals: ["hello world"],
|
|
@@ -3159,8 +2963,7 @@ describe("LiveVoiceSession unified front-door endpointing", () => {
|
|
|
3159
2963
|
await session.handleBinaryAudio(LOUD_CHUNK);
|
|
3160
2964
|
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
3161
2965
|
|
|
3162
|
-
// The leg IS the endpoint decision
|
|
3163
|
-
expect(endpointCalls()).toBe(0);
|
|
2966
|
+
// The leg IS the endpoint decision.
|
|
3164
2967
|
expect(starter.calls).toHaveLength(1);
|
|
3165
2968
|
// Dispatched speculatively on the pre-finalize transcript, as the
|
|
3166
2969
|
// front-door leg with the verdict rule requested.
|
|
@@ -3182,7 +2985,6 @@ describe("LiveVoiceSession unified front-door endpointing", () => {
|
|
|
3182
2985
|
});
|
|
3183
2986
|
|
|
3184
2987
|
test("a hold verdict discards the leg silently and the extension replays the boundary", async () => {
|
|
3185
|
-
enableUnifiedFlags();
|
|
3186
2988
|
const starter = makeVerdictTurnStarter([["[0]"], ["Sure thing."]]);
|
|
3187
2989
|
const { frames, session, transcribers } = createHarness({
|
|
3188
2990
|
finals: ["hello world"],
|
|
@@ -3215,7 +3017,6 @@ describe("LiveVoiceSession unified front-door endpointing", () => {
|
|
|
3215
3017
|
});
|
|
3216
3018
|
|
|
3217
3019
|
test("a verdict that misses the deadline commits the turn (fail-open)", async () => {
|
|
3218
|
-
enableUnifiedFlags();
|
|
3219
3020
|
// The leg never produces a verdict — a provider TTFT tail. The deadline
|
|
3220
3021
|
// must commit the turn so the caller gets the thinking frame (and the
|
|
3221
3022
|
// ack timer arms) instead of unbounded structural silence.
|
|
@@ -3240,7 +3041,6 @@ describe("LiveVoiceSession unified front-door endpointing", () => {
|
|
|
3240
3041
|
});
|
|
3241
3042
|
|
|
3242
3043
|
test("a final that extends the held transcript replays the boundary immediately", async () => {
|
|
3243
|
-
enableUnifiedFlags();
|
|
3244
3044
|
const starter = makeVerdictTurnStarter([["[0]"], ["Got it."]]);
|
|
3245
3045
|
const { frames, session, transcribers } = createHarness({
|
|
3246
3046
|
finals: ["hello world how are you"],
|
|
@@ -3265,7 +3065,6 @@ describe("LiveVoiceSession unified front-door endpointing", () => {
|
|
|
3265
3065
|
});
|
|
3266
3066
|
|
|
3267
3067
|
test("speech resuming mid-verdict discards the leg and the utterance keeps accumulating", async () => {
|
|
3268
|
-
enableUnifiedFlags();
|
|
3269
3068
|
// First leg never produces a verdict (in flight); second answers.
|
|
3270
3069
|
const starter = makeVerdictTurnStarter([[], ["Got it all."]]);
|
|
3271
3070
|
const { frames, session, transcribers } = createHarness({
|
|
@@ -3294,7 +3093,6 @@ describe("LiveVoiceSession unified front-door endpointing", () => {
|
|
|
3294
3093
|
});
|
|
3295
3094
|
|
|
3296
3095
|
test("a discard that beats the bridge handle still rolls back the user row", async () => {
|
|
3297
|
-
enableUnifiedFlags();
|
|
3298
3096
|
const discard = mock(async () => {});
|
|
3299
3097
|
const abort = mock();
|
|
3300
3098
|
let resolveHandle: (() => void) | null = null;
|
|
@@ -3336,7 +3134,6 @@ describe("LiveVoiceSession unified front-door endpointing", () => {
|
|
|
3336
3134
|
});
|
|
3337
3135
|
|
|
3338
3136
|
test("a manual release during the verdict window commits the leg instead of discarding it", async () => {
|
|
3339
|
-
enableUnifiedFlags();
|
|
3340
3137
|
const discard = mock(async () => {});
|
|
3341
3138
|
let callbacks: VoiceTurnCallbacks | undefined;
|
|
3342
3139
|
const calls: VoiceTurnOptions[] = [];
|
|
@@ -3376,7 +3173,6 @@ describe("LiveVoiceSession unified front-door endpointing", () => {
|
|
|
3376
3173
|
});
|
|
3377
3174
|
|
|
3378
3175
|
test("a hold verdict after a manual release relaunches a fresh leg on the released utterance", async () => {
|
|
3379
|
-
enableUnifiedFlags();
|
|
3380
3176
|
const discard = mock(async () => {});
|
|
3381
3177
|
let firstCallbacks: VoiceTurnCallbacks | undefined;
|
|
3382
3178
|
const calls: VoiceTurnOptions[] = [];
|