@vellumai/assistant 0.12.3-staging.1 → 0.12.3-staging.2
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 +43 -29
- package/Dockerfile +63 -0
- package/docs/architecture/security.md +10 -5
- package/docs/architecture/turn-actor.md +2 -0
- package/docs/desktop-browser-cli.md +45 -4
- package/docs/live-voice-task-outcomes.md +100 -0
- package/docs/mode-sessions.md +183 -0
- package/docs/voice-input-diagnostics.md +58 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
- package/openapi.yaml +665 -14
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +10 -26
- package/scripts/smoke-desktop-window-theme.ts +2 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
- package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/answered-question-persistence.test.ts +2 -0
- package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
- package/src/__tests__/approval-message-composer.test.ts +1 -118
- package/src/__tests__/approval-routes-http.test.ts +3 -0
- package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
- package/src/__tests__/assistant-event-targeting.test.ts +186 -0
- package/src/__tests__/assistant-stream-state.test.ts +29 -9
- package/src/__tests__/browser-fill-credential.test.ts +11 -22
- package/src/__tests__/btw-routes.test.ts +4 -1
- package/src/__tests__/build-persisted-content.test.ts +1 -1
- package/src/__tests__/call-conversation-messages.test.ts +5 -0
- package/src/__tests__/call-recovery.test.ts +7 -2
- package/src/__tests__/call-routes-http.test.ts +12 -0
- package/src/__tests__/call-store.test.ts +7 -2
- package/src/__tests__/channel-retry-sweep.test.ts +9 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
- package/src/__tests__/checker.test.ts +11 -3
- package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
- package/src/__tests__/computer-use-tools.test.ts +1 -233
- package/src/__tests__/config-model-image-provider.test.ts +39 -0
- package/src/__tests__/config-schema.test.ts +16 -0
- package/src/__tests__/config-watcher.test.ts +24 -0
- package/src/__tests__/context-search-agent-runner.test.ts +260 -1
- package/src/__tests__/context-search-fanout.test.ts +1 -1
- package/src/__tests__/context-search-types.test.ts +7 -7
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +546 -16
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
- package/src/__tests__/conversation-evictor.test.ts +62 -37
- package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-retry-route.test.ts +22 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
- package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
- package/src/__tests__/conversation-undo.test.ts +61 -0
- package/src/__tests__/credential-execution-client.test.ts +6 -6
- package/src/__tests__/credential-prompt-route.test.ts +35 -3
- package/src/__tests__/cu-unified-flow.test.ts +82 -1
- package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
- package/src/__tests__/dm-backfill.test.ts +91 -1
- package/src/__tests__/document-create-route.test.ts +88 -0
- package/src/__tests__/document-sync-tags.test.ts +27 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
- package/src/__tests__/events-tail-route.test.ts +25 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
- package/src/__tests__/guardian-action-sweep.test.ts +16 -0
- package/src/__tests__/guardian-dispatch.test.ts +13 -0
- package/src/__tests__/headless-browser-interactions.test.ts +64 -20
- package/src/__tests__/heartbeat-service.test.ts +2 -59
- package/src/__tests__/helpers/mock-conversation.ts +21 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
- package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
- package/src/__tests__/host-cu-proxy.test.ts +46 -2
- package/src/__tests__/http-user-message-parity.test.ts +2 -0
- package/src/__tests__/image-credentials.test.ts +49 -0
- package/src/__tests__/image-service-dispatcher.test.ts +45 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
- package/src/__tests__/interrupt-on-send.test.ts +57 -43
- package/src/__tests__/interrupt-turn-note.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +49 -1
- package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
- package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
- package/src/__tests__/mode-session-history.test.ts +245 -0
- package/src/__tests__/mode-session-recovery.test.ts +222 -0
- package/src/__tests__/mode-session-wire.test.ts +141 -0
- package/src/__tests__/notification-guardian-path.test.ts +7 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
- package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/plugin-api-provider.test.ts +5 -0
- package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
- package/src/__tests__/process-message-display-content.test.ts +56 -0
- package/src/__tests__/reaction-persistence.test.ts +5 -0
- package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
- package/src/__tests__/secret-ingress-http.test.ts +2 -0
- package/src/__tests__/secure-keys.test.ts +43 -1
- package/src/__tests__/send-endpoint-busy.test.ts +163 -1
- package/src/__tests__/server-history-render.test.ts +91 -1
- package/src/__tests__/settings-routes.test.ts +50 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
- package/src/__tests__/subagent-manager-notify.test.ts +1 -2
- package/src/__tests__/subagent-notify-parent.test.ts +146 -39
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
- package/src/__tests__/thread-backfill.test.ts +53 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/twilio-routes.test.ts +1 -0
- package/src/__tests__/web-search-backend-failure.test.ts +1 -1
- package/src/activation/progress-store.test.ts +12 -7
- package/src/agent/loop.ts +99 -41
- package/src/api/events/assistant-turn-start.ts +3 -0
- package/src/api/events/generation-handoff.ts +2 -0
- package/src/api/events/message-complete.ts +2 -0
- package/src/api/events/notification-conversation-created.ts +14 -16
- package/src/api/events/notification-intent.ts +4 -2
- package/src/api/events/tool-result.ts +85 -0
- package/src/api/events/tool-use-start.ts +3 -0
- package/src/api/events/user-message-echo.ts +3 -0
- package/src/api/index.ts +31 -6
- package/src/api/mode-session.test.ts +131 -0
- package/src/api/mode-session.ts +148 -0
- package/src/api/remember-tool.ts +15 -0
- package/src/api/responses/conversation-message.ts +6 -0
- package/src/approvals/AGENTS.md +4 -2
- package/src/browser/__tests__/operations.test.ts +29 -0
- package/src/browser/operation-meta.ts +2 -1
- package/src/browser/operations.ts +29 -0
- package/src/browser/virtual-desktop-target.ts +2 -8
- package/src/bundler/bundle-signer.ts +1 -3
- package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
- package/src/calls/__tests__/call-controller.test.ts +94 -0
- package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
- package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
- package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
- package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
- package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
- package/src/calls/barge-in-guard.ts +5 -0
- package/src/calls/call-controller.ts +196 -1
- package/src/calls/call-domain.ts +4 -0
- package/src/calls/call-store.ts +87 -2
- package/src/calls/guardian-action-sweep.ts +22 -8
- package/src/calls/media-stream-audio-transcode.ts +1 -3
- package/src/calls/typesafe-noul.ts +156 -0
- package/src/calls/voice-control-protocol.ts +6 -0
- package/src/calls/voice-escalation-judge.ts +145 -0
- package/src/calls/voice-escalation-target.ts +10 -0
- package/src/calls/voice-leg-coordinator.ts +121 -3
- package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
- package/src/calls/voice-session-bridge.ts +256 -112
- package/src/calls/voice-triage-escalate.ts +7 -0
- package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
- package/src/cli/commands/__tests__/notifications.test.ts +35 -10
- package/src/cli/commands/browser.help.ts +4 -5
- package/src/cli/commands/browser.ts +0 -1
- package/src/cli/commands/clients.help.ts +46 -0
- package/src/cli/commands/clients.ts +116 -0
- package/src/cli/commands/credentials.help.ts +6 -1
- package/src/cli/commands/notifications.help.ts +16 -2
- package/src/cli/commands/notifications.ts +30 -8
- package/src/cli/commands/roadmap.help.ts +18 -21
- package/src/cli/commands/roadmap.ts +4 -21
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
- package/src/cli/lib/bundled-marketplace.json +84 -34
- package/src/cli/lib/plugin-marketplace.ts +8 -0
- package/src/cli/lib/publish-plugin.ts +1 -2
- package/src/config/__tests__/default-provider.test.ts +3 -1
- package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
- package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
- package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
- package/src/config/call-site-defaults.ts +9 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/profile-text-generation.test.ts +2 -2
- package/src/config/schemas/__tests__/tools.test.ts +31 -0
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -18
- package/src/config/schemas/llm.ts +3 -1
- package/src/config/schemas/mcp.ts +2 -1
- package/src/config/schemas/memory-retrospective.ts +11 -2
- package/src/config/schemas/services.ts +15 -6
- package/src/config/schemas/tools.ts +10 -0
- package/src/config/session-groups-gate.ts +21 -0
- package/src/config/skills.ts +2 -6
- package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
- package/src/conversations/message-consolidation.ts +102 -5
- package/src/credential-execution/ces-connect.test.ts +69 -0
- package/src/credential-execution/ces-connect.ts +115 -0
- package/src/credential-execution/ces-runtime.ts +55 -93
- package/src/credential-execution/executable-discovery.ts +1 -11
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
- package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
- package/src/daemon/browser-mode-session.test.ts +402 -0
- package/src/daemon/browser-mode-session.ts +186 -0
- package/src/daemon/computer-use-mode-session.test.ts +210 -0
- package/src/daemon/computer-use-mode-session.ts +105 -0
- package/src/daemon/config-watcher.ts +10 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
- package/src/daemon/conversation-agent-loop.ts +219 -25
- package/src/daemon/conversation-evictor.ts +5 -24
- package/src/daemon/conversation-history.ts +10 -0
- package/src/daemon/conversation-interrupt-repair.ts +4 -43
- package/src/daemon/conversation-interrupt.ts +7 -11
- package/src/daemon/conversation-messaging.ts +58 -5
- package/src/daemon/conversation-mode-session.test.ts +1667 -0
- package/src/daemon/conversation-mode-session.ts +1008 -0
- package/src/daemon/conversation-process.ts +5 -0
- package/src/daemon/conversation-runtime-assembly.ts +10 -20
- package/src/daemon/conversation-skill-tools.ts +1 -0
- package/src/daemon/conversation-store.ts +4 -4
- package/src/daemon/conversation-surface-snapshots.ts +135 -0
- package/src/daemon/conversation-surfaces.ts +113 -5
- package/src/daemon/conversation-tool-setup.ts +8 -3
- package/src/daemon/conversation.ts +143 -41
- package/src/daemon/handlers/config-model.ts +7 -5
- package/src/daemon/handlers/shared.ts +55 -16
- package/src/daemon/host-cu-proxy.ts +51 -22
- package/src/daemon/lifecycle.ts +30 -13
- package/src/daemon/message-protocol.ts +0 -1
- package/src/daemon/message-provenance.ts +28 -4
- package/src/daemon/mode-session-startup-recovery.ts +23 -0
- package/src/daemon/mode-session-tracking.ts +54 -0
- package/src/daemon/process-message.ts +13 -4
- package/src/daemon/virtual-desktop-context.ts +21 -0
- package/src/desktop/__tests__/fake-desktop.ts +1 -0
- package/src/desktop/desktop-accessibility-script.ts +88 -0
- package/src/desktop/desktop-accessibility.test.ts +160 -0
- package/src/desktop/desktop-accessibility.ts +169 -0
- package/src/desktop/desktop-automation-lease.test.ts +76 -68
- package/src/desktop/desktop-automation-lease.ts +41 -50
- package/src/desktop/desktop-browser-endpoint.ts +4 -0
- package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
- package/src/desktop/desktop-computer-use.test.ts +449 -0
- package/src/desktop/desktop-computer-use.ts +502 -0
- package/src/desktop/desktop-dependencies.ts +47 -293
- package/src/desktop/desktop-display.ts +2 -2
- package/src/desktop/desktop-panel-config.test.ts +13 -3
- package/src/desktop/desktop-panel-config.ts +109 -15
- package/src/desktop/desktop-session-manager.test.ts +69 -21
- package/src/desktop/desktop-session-manager.ts +76 -22
- package/src/desktop/desktop-stream-bridge.test.ts +11 -122
- package/src/desktop/desktop-stream-bridge.ts +6 -8
- package/src/desktop/desktop-window-theme.ts +5 -25
- package/src/desktop/virtual-desktop-feature.ts +52 -2
- package/src/desktop/virtual-desktop-platform.test.ts +5 -9
- package/src/documents/document-store.ts +23 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
- package/src/heartbeat/heartbeat-service.ts +5 -33
- package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
- package/src/ipc/assistant-server.ts +2 -0
- package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
- package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
- package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
- package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
- package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
- package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
- package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
- package/src/live-voice/__tests__/session-controls.test.ts +23 -0
- package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
- package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
- package/src/live-voice/camera-mode-session.test.ts +265 -0
- package/src/live-voice/camera-mode-session.ts +277 -0
- package/src/live-voice/continuation-judge.ts +123 -0
- package/src/live-voice/live-activity-reporter.ts +115 -6
- package/src/live-voice/live-voice-manager.ts +36 -1
- package/src/live-voice/live-voice-photo.ts +88 -7
- package/src/live-voice/live-voice-session-manager.ts +15 -0
- package/src/live-voice/live-voice-session.ts +1443 -747
- package/src/live-voice/live-voice-tts.ts +22 -4
- package/src/live-voice/microphone-idle-gate.ts +56 -0
- package/src/live-voice/protocol.ts +171 -4
- package/src/live-voice/session-controls.ts +11 -3
- package/src/live-voice/task-outcomes.ts +131 -0
- package/src/live-voice/voice-input-diagnostics.ts +117 -0
- package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
- package/src/mcp/__tests__/tool-caps.test.ts +28 -1
- package/src/mcp/manager.ts +3 -0
- package/src/mcp/tool-caps.ts +12 -4
- package/src/media/__tests__/image-models.test.ts +58 -0
- package/src/media/image-credentials.ts +5 -0
- package/src/media/image-models.ts +52 -0
- package/src/media/image-service.ts +8 -0
- package/src/media/openrouter-image-service.test.ts +133 -0
- package/src/media/openrouter-image-service.ts +153 -0
- package/src/media/types.ts +1 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
- package/src/messaging/providers/discord/send.ts +4 -17
- package/src/messaging/providers/discord/transport.ts +2 -2
- package/src/messaging/providers/outlook/client.ts +16 -22
- package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
- package/src/messaging/providers/telegram-bot/send.ts +10 -7
- package/src/messaging/providers/telegram-bot/transport.ts +3 -10
- package/src/messaging/providers/whatsapp/send.test.ts +169 -4
- package/src/monitoring/__tests__/control.test.ts +17 -5
- package/src/monitoring/control.ts +15 -5
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/README.md +83 -99
- package/src/notifications/__tests__/broadcaster.test.ts +1 -1
- package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
- package/src/notifications/__tests__/copy-composer.test.ts +1 -1
- package/src/notifications/__tests__/decision-engine.test.ts +79 -2
- package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
- package/src/notifications/__tests__/notification-utils.test.ts +18 -0
- package/src/notifications/adapters/macos.ts +37 -46
- package/src/notifications/broadcaster.ts +5 -6
- package/src/notifications/channel-allowlist.ts +44 -0
- package/src/notifications/copy-composer.ts +0 -23
- package/src/notifications/decision-engine.ts +24 -14
- package/src/notifications/deterministic-checks.ts +1 -1
- package/src/notifications/emit-signal.ts +123 -37
- package/src/notifications/home-feed-side-effect.ts +9 -7
- package/src/notifications/notification-utils.ts +19 -0
- package/src/notifications/signal.ts +4 -19
- package/src/notifications/types.ts +15 -0
- package/src/oauth/connection-resolver.ts +1 -2
- package/src/oauth/oauth-store.ts +1 -3
- package/src/oauth/provider-serializer.ts +7 -0
- package/src/oauth/seed-providers.ts +30 -0
- package/src/onboarding/onboarding-events-store.ts +99 -12
- package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
- package/src/persistence/client-connection-events-store.ts +377 -0
- package/src/persistence/conversation-crud.ts +55 -6
- package/src/persistence/conversation-mode-sessions.test.ts +424 -0
- package/src/persistence/conversation-mode-sessions.ts +460 -0
- package/src/persistence/conversation-queries.ts +6 -19
- package/src/persistence/llm-usage-store.ts +3 -9
- package/src/persistence/message-metadata.ts +25 -0
- package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
- package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
- package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
- package/src/persistence/schema/conversations.ts +67 -0
- package/src/persistence/schema/infrastructure.ts +29 -0
- package/src/persistence/steps.ts +17 -0
- package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
- package/src/plugin-api/effective-context-window.ts +33 -0
- package/src/plugin-api/index.ts +24 -5
- package/src/plugin-api/plugin-name-env.ts +125 -0
- package/src/plugin-api/resolve-credential.ts +79 -20
- package/src/plugin-api/vision-support.test.ts +22 -0
- package/src/plugin-api/vision-support.ts +17 -5
- package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
- package/src/plugins/active-plugin-env.ts +51 -0
- package/src/plugins/defaults/index.ts +24 -0
- package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
- package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
- package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
- package/src/plugins/defaults/memory/context-search/format.ts +51 -7
- package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
- package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
- package/src/plugins/defaults/memory/context-search/search.ts +8 -14
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
- package/src/plugins/defaults/memory/context-search/types.ts +1 -11
- package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
- package/src/plugins/defaults/memory/graph/tools.ts +35 -64
- package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/tools.test.ts +124 -9
- package/src/plugins/defaults/memory/tools.ts +38 -11
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
- package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
- package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
- package/src/plugins/defaults/memory/worker-control.ts +15 -5
- package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
- package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
- package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
- package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
- package/src/plugins/defaults/task-progress/package.json +14 -0
- package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
- package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
- package/src/providers/anthropic/client.ts +10 -1
- package/src/providers/anthropic-gateway-shared.ts +6 -0
- package/src/providers/fetch-provider-catalog.ts +11 -0
- package/src/providers/gemini/client.ts +25 -8
- package/src/providers/inference/adapter-factory.ts +1 -1
- package/src/providers/jev/client.test.ts +10 -0
- package/src/providers/jev/client.ts +20 -4
- package/src/providers/model-catalog.ts +4 -3
- package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
- package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
- package/src/providers/openai/chat-completions-provider.ts +100 -29
- package/src/providers/openai/google-thought-signature.ts +0 -6
- package/src/providers/openai/responses-provider.ts +23 -1
- package/src/providers/ratelimit.ts +1 -3
- package/src/providers/search-provider-catalog.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
- package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
- package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
- package/src/providers/types.ts +5 -0
- package/src/routes/control.ts +7 -5
- package/src/runtime/AGENTS.md +4 -2
- package/src/runtime/actor-trust-resolver.ts +3 -3
- package/src/runtime/agent-wake.ts +1 -1
- package/src/runtime/approval-message-composer.ts +3 -69
- package/src/runtime/assistant-event-hub.ts +73 -75
- package/src/runtime/assistant-event-publish-options.ts +7 -0
- package/src/runtime/assistant-event-targeting.ts +79 -0
- package/src/runtime/assistant-stream-state.ts +13 -100
- package/src/runtime/background-job-runner.ts +5 -5
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/guardian-action-message-composer.ts +8 -161
- package/src/runtime/guardian-reply-router.ts +28 -32
- package/src/runtime/http-types.ts +6 -0
- package/src/runtime/message-composer-types.ts +3 -61
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
- package/src/runtime/migrations/vbundle-builder.ts +24 -1
- package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
- package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
- package/src/runtime/migrations/vbundle-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-validator.ts +1 -0
- package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
- package/src/runtime/pending-interactions.ts +29 -0
- package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
- package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
- package/src/runtime/routes/browser-context.ts +31 -11
- package/src/runtime/routes/browser-routes.ts +43 -4
- package/src/runtime/routes/btw-routes.ts +9 -4
- package/src/runtime/routes/client-routes.ts +108 -0
- package/src/runtime/routes/conversation-management-routes.ts +4 -1
- package/src/runtime/routes/conversation-routes.ts +113 -27
- package/src/runtime/routes/credential-prompt-routes.ts +4 -3
- package/src/runtime/routes/credential-routes.ts +1 -2
- package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
- package/src/runtime/routes/desktop-setup-routes.ts +4 -6
- package/src/runtime/routes/documents-routes.ts +51 -0
- package/src/runtime/routes/email-routes.ts +3 -7
- package/src/runtime/routes/events-routes.ts +75 -54
- package/src/runtime/routes/identity-routes.ts +1 -5
- package/src/runtime/routes/image-generation-routes.ts +8 -14
- package/src/runtime/routes/inbound-message-handler.ts +35 -38
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +73 -2
- package/src/runtime/routes/notification-routes.ts +10 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/plugins-routes.ts +6 -0
- package/src/runtime/routes/roadmap-routes.ts +47 -8
- package/src/runtime/routes/secret-routes.ts +1 -1
- package/src/runtime/routes/settings-routes.ts +10 -4
- package/src/runtime/routes/watcher-routes.ts +1 -3
- package/src/runtime/trust-class.test.ts +7 -6
- package/src/runtime/trust-class.ts +9 -17
- package/src/schedule/__tests__/worker-control.test.ts +36 -5
- package/src/schedule/worker-control.ts +15 -5
- package/src/security/secure-keys.ts +53 -103
- package/src/skills/clawhub.ts +3 -8
- package/src/skills/managed-store.ts +1 -5
- package/src/stt/types.ts +4 -0
- package/src/subagent/notify.ts +54 -34
- package/src/subagent/parent-notification.ts +6 -0
- package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
- package/src/telemetry/phone-call-funnel.ts +144 -0
- package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
- package/src/tools/ask-question/ask-question-tool.ts +6 -4
- package/src/tools/browser/browser-execution.ts +22 -59
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
- package/src/tools/client-os.ts +15 -0
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
- package/src/tools/computer-use/target.ts +42 -0
- package/src/tools/document/document-tool.ts +13 -18
- package/src/tools/execution-target.ts +14 -16
- package/src/tools/host-terminal/host-shell.ts +4 -1
- package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
- package/src/tools/network/__tests__/web-search.test.ts +203 -0
- package/src/tools/network/web-fetch.ts +274 -7
- package/src/tools/network/web-search.ts +468 -6
- package/src/tools/permission-checker.ts +3 -3
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/skills/load.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +11 -2
- package/src/tools/skills/skill-script-runner.ts +7 -0
- package/src/tools/skills/skill-tool-factory.ts +15 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
- package/src/tools/terminal/safe-env.ts +4 -0
- package/src/tools/terminal/shell.ts +2 -0
- package/src/tools/tool-approval-handler.ts +5 -1
- package/src/tools/tool-types.ts +3 -3
- package/src/tools/types.ts +8 -2
- package/src/util/__tests__/worker-process-command.test.ts +5 -4
- package/src/util/abort-reasons.ts +18 -26
- package/src/util/canonicalize-identity.test.ts +21 -0
- package/src/util/canonicalize-identity.ts +16 -0
- package/src/util/errors.ts +13 -3
- package/src/util/provider-error-patterns.ts +3 -0
- package/src/util/worker-process.ts +77 -35
- package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
- package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/computer-use-window-schema.test.ts +0 -24
- package/src/api/events/heartbeat-alert.ts +0 -20
- package/src/daemon/message-types/web-activity.ts +0 -65
- package/src/desktop/desktop-dependencies.test.ts +0 -131
- package/src/tools/computer-use/definitions.ts +0 -607
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gateway's replies to messages it answered itself reach the person
|
|
3
|
+
* through the channel transport, the same one a turn's reply uses.
|
|
4
|
+
*
|
|
5
|
+
* The Bot API call is the boundary: everything between the gateway's request
|
|
6
|
+
* and Telegram's `sendMessage` is the real code, so the assertion is that the
|
|
7
|
+
* text a person would read left for their chat, in the topic they wrote from.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
import { DELIVER_GATEWAY_REPLY_IPC_METHOD } from "@vellumai/gateway-client";
|
|
13
|
+
|
|
14
|
+
type CallTelegramBotApi =
|
|
15
|
+
typeof import("../../../messaging/providers/telegram-bot/api.js").callTelegramBotApi;
|
|
16
|
+
|
|
17
|
+
const callTelegramBotApiMock = mock<CallTelegramBotApi>(
|
|
18
|
+
async () => ({ message_id: 42 }) as never,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
// Spread the actual module so exports this suite does not touch stay
|
|
22
|
+
// importable by files that share the bun process.
|
|
23
|
+
const actualTelegramApi =
|
|
24
|
+
await import("../../../messaging/providers/telegram-bot/api.js");
|
|
25
|
+
mock.module("../../../messaging/providers/telegram-bot/api.js", () => ({
|
|
26
|
+
...actualTelegramApi,
|
|
27
|
+
callTelegramBotApi: (method: string, body: Record<string, unknown>) =>
|
|
28
|
+
callTelegramBotApiMock(method, body),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const { CHANNEL_REPLY_IPC_METHODS } =
|
|
32
|
+
await import("../channel-reply-ipc-routes.js");
|
|
33
|
+
const { BadGatewayError, BadRequestError } =
|
|
34
|
+
await import("../../../runtime/routes/errors.js");
|
|
35
|
+
|
|
36
|
+
const deliverGatewayReply =
|
|
37
|
+
CHANNEL_REPLY_IPC_METHODS[DELIVER_GATEWAY_REPLY_IPC_METHOD]!;
|
|
38
|
+
|
|
39
|
+
// The callback URL exactly as the gateway's Telegram webhook builds it for a
|
|
40
|
+
// message sent in a topic.
|
|
41
|
+
const TELEGRAM_CALLBACK = "http://127.0.0.1:7830/deliver/telegram?threadId=7";
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
callTelegramBotApiMock.mockClear();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("deliver_gateway_reply", () => {
|
|
48
|
+
test("sends the reply to the person's Telegram chat, in their topic", async () => {
|
|
49
|
+
const result = await deliverGatewayReply({
|
|
50
|
+
body: {
|
|
51
|
+
callbackUrl: TELEGRAM_CALLBACK,
|
|
52
|
+
chatId: "12345",
|
|
53
|
+
text: "Welcome! You've been granted access.",
|
|
54
|
+
assistantId: "self",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const sends = callTelegramBotApiMock.mock.calls.filter(
|
|
59
|
+
([method]) => method === "sendMessage",
|
|
60
|
+
);
|
|
61
|
+
expect(sends).toHaveLength(1);
|
|
62
|
+
expect(sends[0]![1]).toMatchObject({
|
|
63
|
+
chat_id: "12345",
|
|
64
|
+
text: "Welcome! You've been granted access.",
|
|
65
|
+
message_thread_id: 7,
|
|
66
|
+
});
|
|
67
|
+
expect(result).toEqual({ ok: true, messageIds: ["42"] });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("answers a send the channel refuses with a status, not a bare error", async () => {
|
|
71
|
+
// Only a RouteError crosses IPC with a status; without one the gateway
|
|
72
|
+
// reads the refusal as a daemon that never answered.
|
|
73
|
+
callTelegramBotApiMock.mockImplementationOnce(async () => {
|
|
74
|
+
throw new Error("Bad Request: chat not found");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const rejection = expect(
|
|
78
|
+
deliverGatewayReply({
|
|
79
|
+
body: {
|
|
80
|
+
callbackUrl: TELEGRAM_CALLBACK,
|
|
81
|
+
chatId: "12345",
|
|
82
|
+
text: "Welcome! You've been granted access.",
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
).rejects;
|
|
86
|
+
await rejection.toBeInstanceOf(BadGatewayError);
|
|
87
|
+
await rejection.toMatchObject({ statusCode: 502 });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("refuses a callback no channel transport owns, sending nothing", async () => {
|
|
91
|
+
await expect(
|
|
92
|
+
deliverGatewayReply({
|
|
93
|
+
body: {
|
|
94
|
+
callbackUrl: "http://127.0.0.1:7830/deliver/email",
|
|
95
|
+
chatId: "someone@example.com",
|
|
96
|
+
text: "Verification successful.",
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
).rejects.toBeInstanceOf(BadRequestError);
|
|
100
|
+
expect(callTelegramBotApiMock).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("refuses a request with no text, sending nothing", async () => {
|
|
104
|
+
await expect(
|
|
105
|
+
deliverGatewayReply({
|
|
106
|
+
body: { callbackUrl: TELEGRAM_CALLBACK, chatId: "12345", text: "" },
|
|
107
|
+
}),
|
|
108
|
+
).rejects.toBeInstanceOf(BadRequestError);
|
|
109
|
+
expect(callTelegramBotApiMock).not.toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IPC-only channel reply method called by the gateway over the assistant IPC
|
|
3
|
+
* socket (`ipcCallAssistant`).
|
|
4
|
+
*
|
|
5
|
+
* The gateway answers some inbound messages itself without forwarding them
|
|
6
|
+
* (a verification code, an invite redemption), so the assistant never sees
|
|
7
|
+
* them. Its reply still has to reach the person, and the channel transports
|
|
8
|
+
* that can send it live here. This hands the reply to the transport the
|
|
9
|
+
* inbound message's callback URL names, which is how the daemon's own
|
|
10
|
+
* intercepts answer an inbound message: the gateway sends through the same
|
|
11
|
+
* transports and never to a provider itself.
|
|
12
|
+
*
|
|
13
|
+
* Nothing is recorded in a conversation. The message the reply answers never
|
|
14
|
+
* entered one, so a row for the reply would stand alone in a transcript that
|
|
15
|
+
* does not hold what it responds to.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
DELIVER_GATEWAY_REPLY_IPC_METHOD,
|
|
20
|
+
GatewayReplyRequestSchema,
|
|
21
|
+
} from "@vellumai/gateway-client";
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
deliverDirect,
|
|
25
|
+
isDirectDelivery,
|
|
26
|
+
} from "../../messaging/providers/index.js";
|
|
27
|
+
import {
|
|
28
|
+
BadGatewayError,
|
|
29
|
+
BadRequestError,
|
|
30
|
+
} from "../../runtime/routes/errors.js";
|
|
31
|
+
import { parseBody } from "../../runtime/routes/parse-body.js";
|
|
32
|
+
import type { RouteHandlerArgs } from "../../runtime/routes/types.js";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Deliver a gateway-composed reply through the channel transport its callback
|
|
36
|
+
* URL names. Refuses a callback no transport owns rather than fetching it, so
|
|
37
|
+
* the gateway learns the channel cannot be answered this way.
|
|
38
|
+
*
|
|
39
|
+
* A send the channel refuses is answered as a `BadGatewayError`, as
|
|
40
|
+
* `channels/send` answers it: only a `RouteError` carries a status over IPC,
|
|
41
|
+
* and the gateway reads an error without one as a daemon that never answered.
|
|
42
|
+
*/
|
|
43
|
+
export async function handleDeliverGatewayReply({
|
|
44
|
+
body = {},
|
|
45
|
+
}: RouteHandlerArgs) {
|
|
46
|
+
const { callbackUrl, chatId, text, assistantId } = parseBody(
|
|
47
|
+
GatewayReplyRequestSchema,
|
|
48
|
+
body,
|
|
49
|
+
);
|
|
50
|
+
if (!isDirectDelivery(callbackUrl)) {
|
|
51
|
+
throw new BadRequestError("No channel transport owns this callback URL");
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return await deliverDirect(callbackUrl, {
|
|
55
|
+
chatId,
|
|
56
|
+
text,
|
|
57
|
+
...(assistantId ? { assistantId } : {}),
|
|
58
|
+
});
|
|
59
|
+
} catch (err) {
|
|
60
|
+
throw new BadGatewayError(err instanceof Error ? err.message : String(err));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* IPC-only channel reply methods, keyed by IPC operationId. Registered
|
|
66
|
+
* directly on the assistant IPC server (see `assistant-server.ts`).
|
|
67
|
+
*/
|
|
68
|
+
export const CHANNEL_REPLY_IPC_METHODS: Record<
|
|
69
|
+
string,
|
|
70
|
+
(args: RouteHandlerArgs) => unknown
|
|
71
|
+
> = {
|
|
72
|
+
[DELIVER_GATEWAY_REPLY_IPC_METHOD]: handleDeliverGatewayReply,
|
|
73
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
Message,
|
|
5
|
+
Provider,
|
|
6
|
+
ProviderResponse,
|
|
7
|
+
SendMessageOptions,
|
|
8
|
+
} from "../../providers/types.js";
|
|
9
|
+
import {
|
|
10
|
+
CONTINUATION_JUDGE_KEEP_THRESHOLD,
|
|
11
|
+
createContinuationJudge,
|
|
12
|
+
VOICE_CONTINUATION_JUDGE_CALL_SITE,
|
|
13
|
+
} from "../continuation-judge.js";
|
|
14
|
+
|
|
15
|
+
function jevResponse(noul: number): ProviderResponse {
|
|
16
|
+
const answers = { answer: { type: "noul", noul } };
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: JSON.stringify(answers) }],
|
|
19
|
+
model: "jev-latest",
|
|
20
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
21
|
+
stopReason: "end_turn",
|
|
22
|
+
rawResponse: { model: "jev-latest", answers },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function fakeProvider(
|
|
27
|
+
respond: (
|
|
28
|
+
messages: Message[],
|
|
29
|
+
options?: SendMessageOptions,
|
|
30
|
+
) => Promise<ProviderResponse>,
|
|
31
|
+
): Provider {
|
|
32
|
+
return { name: "typesafe", sendMessage: respond } as Provider;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const baseArgs = {
|
|
36
|
+
parentConversationId: "conv-1",
|
|
37
|
+
interruptedRequest: "Can you hit join now for me?",
|
|
38
|
+
signal: new AbortController().signal,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
describe("createContinuationJudge", () => {
|
|
42
|
+
test("drops below the threshold and asks with both utterances", async () => {
|
|
43
|
+
let seen: { body: unknown; options?: SendMessageOptions } | null = null;
|
|
44
|
+
const judge = createContinuationJudge({
|
|
45
|
+
resolveProvider: async () =>
|
|
46
|
+
fakeProvider(async (messages, options) => {
|
|
47
|
+
const block = messages[0]!.content[0]!;
|
|
48
|
+
seen = {
|
|
49
|
+
body: JSON.parse(block.type === "text" ? block.text : ""),
|
|
50
|
+
options,
|
|
51
|
+
};
|
|
52
|
+
return jevResponse(0.09);
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const judgement = await judge({
|
|
57
|
+
...baseArgs,
|
|
58
|
+
interruption: Promise.resolve("Actually just point it out for me."),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(judgement).toMatchObject({
|
|
62
|
+
keep: false,
|
|
63
|
+
outcome: "drop",
|
|
64
|
+
noul: 0.09,
|
|
65
|
+
});
|
|
66
|
+
const request = seen as unknown as {
|
|
67
|
+
body: { state: Record<string, string> };
|
|
68
|
+
options: SendMessageOptions;
|
|
69
|
+
};
|
|
70
|
+
expect(request.body.state).toEqual({
|
|
71
|
+
interrupted_request: "Can you hit join now for me?",
|
|
72
|
+
caller_then_said: "Actually just point it out for me.",
|
|
73
|
+
});
|
|
74
|
+
expect(
|
|
75
|
+
(request.options.config as { callSite?: string } | undefined)?.callSite,
|
|
76
|
+
).toBe(VOICE_CONTINUATION_JUDGE_CALL_SITE);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("keeps at or above the threshold", async () => {
|
|
80
|
+
const judge = createContinuationJudge({
|
|
81
|
+
resolveProvider: async () =>
|
|
82
|
+
fakeProvider(async () =>
|
|
83
|
+
jevResponse(CONTINUATION_JUDGE_KEEP_THRESHOLD),
|
|
84
|
+
),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const judgement = await judge({
|
|
88
|
+
...baseArgs,
|
|
89
|
+
interruption: Promise.resolve("Oh, also, what's the weather?"),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(judgement).toMatchObject({ keep: true, outcome: "keep" });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("without a TypeSafe provider it keeps and never waits on the interruption", async () => {
|
|
96
|
+
const judge = createContinuationJudge({
|
|
97
|
+
resolveProvider: async () => null,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const judgement = await judge({
|
|
101
|
+
...baseArgs,
|
|
102
|
+
// Never settles: the judge must not await it.
|
|
103
|
+
interruption: new Promise<string | null>(() => {}),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(judgement).toMatchObject({ keep: true, outcome: "unavailable" });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("keeps when no interruption arrived in time", async () => {
|
|
110
|
+
let asked = false;
|
|
111
|
+
const judge = createContinuationJudge({
|
|
112
|
+
resolveProvider: async () =>
|
|
113
|
+
fakeProvider(async () => {
|
|
114
|
+
asked = true;
|
|
115
|
+
return jevResponse(0.05);
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const judgement = await judge({
|
|
120
|
+
...baseArgs,
|
|
121
|
+
interruption: Promise.resolve(null),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(judgement).toMatchObject({
|
|
125
|
+
keep: true,
|
|
126
|
+
outcome: "no_interruption",
|
|
127
|
+
});
|
|
128
|
+
expect(asked).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("a provider error or timeout keeps the continuation", async () => {
|
|
132
|
+
const failing = createContinuationJudge({
|
|
133
|
+
resolveProvider: async () =>
|
|
134
|
+
fakeProvider(async () => {
|
|
135
|
+
throw new Error("503");
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
const hanging = createContinuationJudge({
|
|
139
|
+
timeoutMs: 20,
|
|
140
|
+
resolveProvider: async () =>
|
|
141
|
+
fakeProvider(async () => new Promise<ProviderResponse>(() => {})),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(
|
|
145
|
+
await failing({ ...baseArgs, interruption: Promise.resolve("stop") }),
|
|
146
|
+
).toMatchObject({ keep: true, outcome: "error" });
|
|
147
|
+
expect(
|
|
148
|
+
await hanging({ ...baseArgs, interruption: Promise.resolve("stop") }),
|
|
149
|
+
).toMatchObject({ keep: true, outcome: "timeout" });
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -37,6 +37,10 @@ class RecordingReporter extends LiveActivityReporter {
|
|
|
37
37
|
readonly dispatched: Array<{ phase: string; event: string; detail: string }> =
|
|
38
38
|
[];
|
|
39
39
|
|
|
40
|
+
waitForDispatches(): Promise<void> {
|
|
41
|
+
return this.waitForPendingDispatches();
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
protected override async dispatch(
|
|
41
45
|
phase: string,
|
|
42
46
|
event: "update" | "end",
|
|
@@ -46,11 +50,55 @@ class RecordingReporter extends LiveActivityReporter {
|
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
class BlockingReporter extends LiveActivityReporter {
|
|
54
|
+
readonly dispatched: string[] = [];
|
|
55
|
+
private releaseFirstDispatch: (() => void) | null = null;
|
|
56
|
+
private readonly firstDispatch = new Promise<void>((resolve) => {
|
|
57
|
+
this.releaseFirstDispatch = resolve;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
releaseFirst(): void {
|
|
61
|
+
this.releaseFirstDispatch?.();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
waitForDispatches(): Promise<void> {
|
|
65
|
+
return this.waitForPendingDispatches();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected override async dispatch(phase: string): Promise<void> {
|
|
69
|
+
this.dispatched.push(phase);
|
|
70
|
+
if (this.dispatched.length === 1) {
|
|
71
|
+
await this.firstDispatch;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class TimeoutReporter extends LiveActivityReporter {
|
|
77
|
+
readonly dispatched: string[] = [];
|
|
78
|
+
protected override readonly dispatchTimeoutMs = 1;
|
|
79
|
+
|
|
80
|
+
waitForDispatches(): Promise<void> {
|
|
81
|
+
return this.waitForPendingDispatches();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected override async dispatch(phase: string): Promise<void> {
|
|
85
|
+
this.dispatched.push(phase);
|
|
86
|
+
if (this.dispatched.length > 1) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
await new Promise<void>(() => undefined);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function activity(
|
|
94
|
+
label: string,
|
|
95
|
+
kind?: "escalation",
|
|
96
|
+
): LiveVoiceServerFramePayload {
|
|
50
97
|
return {
|
|
51
98
|
type: "activity",
|
|
52
99
|
turnId: "t1",
|
|
53
100
|
label,
|
|
101
|
+
...(kind ? { kind } : {}),
|
|
54
102
|
} as LiveVoiceServerFramePayload;
|
|
55
103
|
}
|
|
56
104
|
|
|
@@ -61,6 +109,9 @@ describe("phaseForFrame", () => {
|
|
|
61
109
|
);
|
|
62
110
|
expect(phaseForFrame(frame("thinking"), "transcribing")).toBe("thinking");
|
|
63
111
|
expect(phaseForFrame(frame("tts_audio"), "thinking")).toBe("speaking");
|
|
112
|
+
expect(
|
|
113
|
+
phaseForFrame(activity("Working on that", "escalation"), "speaking"),
|
|
114
|
+
).toBe("thinking");
|
|
64
115
|
});
|
|
65
116
|
|
|
66
117
|
test("a discarded utterance returns the floor to the user", () => {
|
|
@@ -96,10 +147,11 @@ describe("phaseForFrame", () => {
|
|
|
96
147
|
});
|
|
97
148
|
|
|
98
149
|
describe("LiveActivityReporter", () => {
|
|
99
|
-
test("reports a phase change once", () => {
|
|
150
|
+
test("reports a phase change once", async () => {
|
|
100
151
|
const reporter = new RecordingReporter("conv-1");
|
|
101
152
|
|
|
102
153
|
reporter.report(frame("thinking"));
|
|
154
|
+
await reporter.waitForDispatches();
|
|
103
155
|
|
|
104
156
|
expect(reporter.dispatched).toEqual([
|
|
105
157
|
{ phase: "thinking", event: "update", detail: "" },
|
|
@@ -107,26 +159,31 @@ describe("LiveActivityReporter", () => {
|
|
|
107
159
|
});
|
|
108
160
|
|
|
109
161
|
// The whole reason this class exists rather than a bare fetch per frame.
|
|
110
|
-
test("does not re-report a phase it is already in", () => {
|
|
162
|
+
test("does not re-report a phase it is already in", async () => {
|
|
111
163
|
const reporter = new RecordingReporter("conv-1");
|
|
112
164
|
|
|
113
165
|
reporter.report(frame("tts_audio"));
|
|
114
166
|
reporter.report(frame("tts_audio"));
|
|
115
167
|
reporter.report(frame("tts_audio"));
|
|
168
|
+
await reporter.waitForDispatches();
|
|
116
169
|
|
|
117
170
|
expect(reporter.dispatched).toEqual([
|
|
118
171
|
{ phase: "speaking", event: "update", detail: "" },
|
|
119
172
|
]);
|
|
120
173
|
});
|
|
121
174
|
|
|
122
|
-
test("reports each genuine transition through a turn", () => {
|
|
175
|
+
test("reports each genuine transition through a turn", async () => {
|
|
123
176
|
const reporter = new RecordingReporter("conv-1");
|
|
124
177
|
|
|
125
178
|
reporter.report(frame("utterance_end"));
|
|
179
|
+
await reporter.waitForDispatches();
|
|
126
180
|
reporter.report(sttFinal("hello there"));
|
|
181
|
+
await reporter.waitForDispatches();
|
|
127
182
|
reporter.report(frame("tts_audio"));
|
|
128
183
|
reporter.report(frame("tts_audio"));
|
|
184
|
+
await reporter.waitForDispatches();
|
|
129
185
|
reporter.report(frame("tts_done"));
|
|
186
|
+
await reporter.waitForDispatches();
|
|
130
187
|
|
|
131
188
|
expect(reporter.dispatched.map((d) => d.phase)).toEqual([
|
|
132
189
|
"transcribing",
|
|
@@ -136,20 +193,63 @@ describe("LiveActivityReporter", () => {
|
|
|
136
193
|
]);
|
|
137
194
|
});
|
|
138
195
|
|
|
139
|
-
test("
|
|
196
|
+
test("serializes platform dispatches in frame order", async () => {
|
|
197
|
+
const reporter = new BlockingReporter("conv-1");
|
|
198
|
+
|
|
199
|
+
reporter.report(frame("tts_audio"));
|
|
200
|
+
reporter.report(activity("", "escalation"));
|
|
201
|
+
await Promise.resolve();
|
|
202
|
+
|
|
203
|
+
expect(reporter.dispatched).toEqual(["speaking"]);
|
|
204
|
+
|
|
205
|
+
reporter.releaseFirst();
|
|
206
|
+
await reporter.waitForDispatches();
|
|
207
|
+
expect(reporter.dispatched).toEqual(["speaking", "thinking"]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("a timed-out dispatch does not pin later phases", async () => {
|
|
211
|
+
const reporter = new TimeoutReporter("conv-1");
|
|
212
|
+
|
|
213
|
+
reporter.report(frame("tts_audio"));
|
|
214
|
+
reporter.report(activity("", "escalation"));
|
|
215
|
+
await reporter.waitForDispatches();
|
|
216
|
+
|
|
217
|
+
expect(reporter.dispatched).toEqual(["speaking", "thinking"]);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("coalesces a queued backlog and keeps end next", async () => {
|
|
221
|
+
const reporter = new BlockingReporter("conv-1");
|
|
222
|
+
|
|
223
|
+
reporter.report(frame("utterance_end"));
|
|
224
|
+
reporter.report(frame("thinking"));
|
|
225
|
+
reporter.report(activity("Running a command"));
|
|
226
|
+
reporter.report(frame("tts_audio"));
|
|
227
|
+
reporter.end();
|
|
228
|
+
await Promise.resolve();
|
|
229
|
+
|
|
230
|
+
expect(reporter.dispatched).toEqual(["transcribing"]);
|
|
231
|
+
|
|
232
|
+
reporter.releaseFirst();
|
|
233
|
+
await reporter.waitForDispatches();
|
|
234
|
+
expect(reporter.dispatched).toEqual(["transcribing", "ending"]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("frames that do not move the phase are ignored", async () => {
|
|
140
238
|
const reporter = new RecordingReporter("conv-1");
|
|
141
239
|
|
|
142
240
|
reporter.report(frame("stt_partial"));
|
|
143
241
|
reporter.report(frame("ready"));
|
|
242
|
+
await reporter.waitForDispatches();
|
|
144
243
|
|
|
145
244
|
expect(reporter.dispatched).toEqual([]);
|
|
146
245
|
});
|
|
147
246
|
|
|
148
|
-
test("ending retires the activity", () => {
|
|
247
|
+
test("ending retires the activity", async () => {
|
|
149
248
|
const reporter = new RecordingReporter("conv-1");
|
|
150
249
|
reporter.report(frame("tts_audio"));
|
|
151
250
|
|
|
152
251
|
reporter.end();
|
|
252
|
+
await reporter.waitForDispatches();
|
|
153
253
|
|
|
154
254
|
expect(reporter.dispatched.at(-1)).toEqual({
|
|
155
255
|
phase: "ending",
|
|
@@ -161,20 +261,22 @@ describe("LiveActivityReporter", () => {
|
|
|
161
261
|
// A session can end more than one way and the platform deletes the
|
|
162
262
|
// registration on the first, so a second end would push at an activity that
|
|
163
263
|
// is already gone.
|
|
164
|
-
test("ending twice pushes once", () => {
|
|
264
|
+
test("ending twice pushes once", async () => {
|
|
165
265
|
const reporter = new RecordingReporter("conv-1");
|
|
166
266
|
|
|
167
267
|
reporter.end();
|
|
168
268
|
reporter.end();
|
|
269
|
+
await reporter.waitForDispatches();
|
|
169
270
|
|
|
170
271
|
expect(reporter.dispatched).toHaveLength(1);
|
|
171
272
|
});
|
|
172
273
|
|
|
173
|
-
test("a frame after the end is not reported", () => {
|
|
274
|
+
test("a frame after the end is not reported", async () => {
|
|
174
275
|
const reporter = new RecordingReporter("conv-1");
|
|
175
276
|
|
|
176
277
|
reporter.end();
|
|
177
278
|
reporter.report(frame("tts_audio"));
|
|
279
|
+
await reporter.waitForDispatches();
|
|
178
280
|
|
|
179
281
|
expect(reporter.dispatched).toEqual([
|
|
180
282
|
{ phase: "ending", event: "end", detail: "" },
|
|
@@ -183,12 +285,26 @@ describe("LiveActivityReporter", () => {
|
|
|
183
285
|
});
|
|
184
286
|
|
|
185
287
|
describe("LiveActivityReporter activity line", () => {
|
|
186
|
-
test("
|
|
288
|
+
test("keeps escalation out of the Live Activity detail line", async () => {
|
|
289
|
+
const reporter = new RecordingReporter("conv-1");
|
|
290
|
+
|
|
291
|
+
reporter.report(frame("tts_audio"));
|
|
292
|
+
reporter.report(activity("Working on that", "escalation"));
|
|
293
|
+
await reporter.waitForDispatches();
|
|
294
|
+
|
|
295
|
+
expect(reporter.dispatched).toEqual([
|
|
296
|
+
{ phase: "speaking", event: "update", detail: "" },
|
|
297
|
+
{ phase: "thinking", event: "update", detail: "" },
|
|
298
|
+
]);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("carries the activity label alongside the phase it holds", async () => {
|
|
187
302
|
const reporter = new RecordingReporter("conv-1");
|
|
188
303
|
|
|
189
304
|
reporter.report(frame("utterance_end"));
|
|
190
305
|
reporter.report(frame("thinking"));
|
|
191
306
|
reporter.report(activity("Running a command"));
|
|
307
|
+
await reporter.waitForDispatches();
|
|
192
308
|
|
|
193
309
|
expect(reporter.dispatched.at(-1)).toEqual({
|
|
194
310
|
phase: "thinking",
|
|
@@ -199,12 +315,13 @@ describe("LiveActivityReporter activity line", () => {
|
|
|
199
315
|
|
|
200
316
|
// A push replaces the whole content state, so the label has to ride along on
|
|
201
317
|
// phase changes too, or the next phase would blank it.
|
|
202
|
-
test("keeps the label through a later phase change", () => {
|
|
318
|
+
test("keeps the label through a later phase change", async () => {
|
|
203
319
|
const reporter = new RecordingReporter("conv-1");
|
|
204
320
|
|
|
205
321
|
reporter.report(frame("thinking"));
|
|
206
322
|
reporter.report(activity("Reading a file"));
|
|
207
323
|
reporter.report(frame("tts_audio"));
|
|
324
|
+
await reporter.waitForDispatches();
|
|
208
325
|
|
|
209
326
|
expect(reporter.dispatched.at(-1)).toEqual({
|
|
210
327
|
phase: "speaking",
|
|
@@ -213,36 +330,56 @@ describe("LiveActivityReporter activity line", () => {
|
|
|
213
330
|
});
|
|
214
331
|
});
|
|
215
332
|
|
|
216
|
-
test("
|
|
333
|
+
test("restores thinking after bridge speech without clearing an overlay", async () => {
|
|
334
|
+
const reporter = new RecordingReporter("conv-1");
|
|
335
|
+
|
|
336
|
+
reporter.report(frame("thinking"));
|
|
337
|
+
reporter.report(frame("tts_audio"));
|
|
338
|
+
reporter.report(activity("Searching the web"));
|
|
339
|
+
reporter.restoreThinkingPhase();
|
|
340
|
+
await reporter.waitForDispatches();
|
|
341
|
+
|
|
342
|
+
expect(reporter.dispatched.at(-1)).toEqual({
|
|
343
|
+
phase: "thinking",
|
|
344
|
+
event: "update",
|
|
345
|
+
detail: "Searching the web",
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("an empty label clears the line", async () => {
|
|
217
350
|
const reporter = new RecordingReporter("conv-1");
|
|
218
351
|
|
|
219
352
|
reporter.report(frame("thinking"));
|
|
220
353
|
reporter.report(activity("Reading a file"));
|
|
221
354
|
reporter.report(activity(""));
|
|
355
|
+
await reporter.waitForDispatches();
|
|
222
356
|
|
|
223
357
|
expect(reporter.dispatched.at(-1)?.detail).toBe("");
|
|
224
358
|
});
|
|
225
359
|
|
|
226
|
-
test("does not dispatch a label it already sent", () => {
|
|
360
|
+
test("does not dispatch a label it already sent", async () => {
|
|
227
361
|
const reporter = new RecordingReporter("conv-1");
|
|
228
362
|
|
|
229
363
|
reporter.report(frame("thinking"));
|
|
230
364
|
reporter.report(activity("Reading a file"));
|
|
365
|
+
await reporter.waitForDispatches();
|
|
231
366
|
const before = reporter.dispatched.length;
|
|
232
367
|
reporter.report(activity("Reading a file"));
|
|
368
|
+
await reporter.waitForDispatches();
|
|
233
369
|
|
|
234
370
|
expect(reporter.dispatched.length).toBe(before);
|
|
235
371
|
});
|
|
236
372
|
|
|
237
373
|
// A tool can start before any phase-bearing frame lands. There is no content
|
|
238
374
|
// state to attach a label to yet, and the phase that follows carries it.
|
|
239
|
-
test("holds a label that arrives before any phase", () => {
|
|
375
|
+
test("holds a label that arrives before any phase", async () => {
|
|
240
376
|
const reporter = new RecordingReporter("conv-1");
|
|
241
377
|
|
|
242
378
|
reporter.report(activity("Searching the web"));
|
|
243
379
|
expect(reporter.dispatched).toEqual([]);
|
|
244
380
|
|
|
245
381
|
reporter.report(frame("thinking"));
|
|
382
|
+
await reporter.waitForDispatches();
|
|
246
383
|
|
|
247
384
|
expect(reporter.dispatched).toEqual([
|
|
248
385
|
{ phase: "thinking", event: "update", detail: "Searching the web" },
|
|
@@ -253,19 +390,21 @@ describe("LiveActivityReporter activity line", () => {
|
|
|
253
390
|
describe("LiveActivityReporter and held utterances", () => {
|
|
254
391
|
// End to end through the reporter, not just the mapping: a session using
|
|
255
392
|
// semantic endpointing emits finals while the user still holds the floor.
|
|
256
|
-
test("a final during a held utterance pushes nothing", () => {
|
|
393
|
+
test("a final during a held utterance pushes nothing", async () => {
|
|
257
394
|
const reporter = new RecordingReporter("conv-1");
|
|
258
395
|
|
|
259
396
|
reporter.report(sttFinal("i was thinking maybe"));
|
|
397
|
+
await reporter.waitForDispatches();
|
|
260
398
|
|
|
261
399
|
expect(reporter.dispatched).toEqual([]);
|
|
262
400
|
});
|
|
263
401
|
|
|
264
|
-
test("the same final after utterance_end advances to thinking", () => {
|
|
402
|
+
test("the same final after utterance_end advances to thinking", async () => {
|
|
265
403
|
const reporter = new RecordingReporter("conv-1");
|
|
266
404
|
|
|
267
405
|
reporter.report(frame("utterance_end"));
|
|
268
406
|
reporter.report(sttFinal("i was thinking maybe"));
|
|
407
|
+
await reporter.waitForDispatches();
|
|
269
408
|
|
|
270
409
|
expect(reporter.dispatched.map((d) => d.phase)).toEqual([
|
|
271
410
|
"transcribing",
|