@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
|
@@ -55,6 +55,8 @@ export type BargeInGuardStep = "pending" | "reset" | "fired";
|
|
|
55
55
|
export interface BargeInGuard {
|
|
56
56
|
/** Speech accumulated in the current run. */
|
|
57
57
|
readonly speechMs: number;
|
|
58
|
+
/** Whether this run has met the threshold, latched until the guard is replaced. */
|
|
59
|
+
readonly fired: boolean;
|
|
58
60
|
/** Feed one classified chunk of `chunkMs` audio. */
|
|
59
61
|
track(
|
|
60
62
|
classification: BargeInChunkClassification,
|
|
@@ -82,6 +84,9 @@ export function createBargeInGuard(minSpeechMs: number): BargeInGuard {
|
|
|
82
84
|
};
|
|
83
85
|
|
|
84
86
|
return {
|
|
87
|
+
get fired() {
|
|
88
|
+
return fired;
|
|
89
|
+
},
|
|
85
90
|
get speechMs() {
|
|
86
91
|
return speechMs;
|
|
87
92
|
},
|
|
@@ -94,6 +94,11 @@ import {
|
|
|
94
94
|
type FrontDoorLegCoordinator,
|
|
95
95
|
type SpokenEscalationBridge,
|
|
96
96
|
} from "./voice-leg-coordinator.js";
|
|
97
|
+
import {
|
|
98
|
+
getVoiceMetricsAggregateFields,
|
|
99
|
+
VoiceMetricsCollector,
|
|
100
|
+
type VoiceMetricsSnapshot,
|
|
101
|
+
} from "./voice-metrics.js";
|
|
97
102
|
import { createProgressCadence } from "./voice-progress-cadence.js";
|
|
98
103
|
import {
|
|
99
104
|
CONVERSATION_BUSY_MESSAGE,
|
|
@@ -248,6 +253,23 @@ export class CallController {
|
|
|
248
253
|
private readonly progressConfig: VoiceProgressConfig;
|
|
249
254
|
/** Rotates the static narration fallback across turns. */
|
|
250
255
|
private progressPhraseCounter = 0;
|
|
256
|
+
/** Per-turn latency marks for the call, shared with live voice. */
|
|
257
|
+
private readonly metrics: VoiceMetricsCollector;
|
|
258
|
+
/**
|
|
259
|
+
* The turn the collector currently has open, or null between turns. Every
|
|
260
|
+
* mark is guarded on this: the collector opens a turn for any mark that
|
|
261
|
+
* arrives without one, so an unguarded late callback would invent a phantom
|
|
262
|
+
* turn that never settles.
|
|
263
|
+
*/
|
|
264
|
+
private metricsTurnId: string | null = null;
|
|
265
|
+
/**
|
|
266
|
+
* When the caller's utterance was transcribed, held until the turn it
|
|
267
|
+
* dispatches opens. The phone commits on the provider's utterance-boundary
|
|
268
|
+
* final, so this is the turn's speech-end anchor as well as its transcript
|
|
269
|
+
* mark; seeding it backdates the turn to the moment the caller stopped
|
|
270
|
+
* talking rather than the moment the controller got around to dispatching.
|
|
271
|
+
*/
|
|
272
|
+
private pendingFinalTranscriptAtMs: number | null = null;
|
|
251
273
|
|
|
252
274
|
constructor(
|
|
253
275
|
callSessionId: string,
|
|
@@ -285,6 +307,11 @@ export class CallController {
|
|
|
285
307
|
this.conversationId = session?.conversationId ?? callSessionId;
|
|
286
308
|
this.skipDisclosure = session?.skipDisclosure ?? false;
|
|
287
309
|
|
|
310
|
+
this.metrics = new VoiceMetricsCollector({
|
|
311
|
+
sessionId: callSessionId,
|
|
312
|
+
conversationId: this.conversationId,
|
|
313
|
+
});
|
|
314
|
+
|
|
288
315
|
this.startDurationTimer();
|
|
289
316
|
this.resetSilenceTimer();
|
|
290
317
|
registerCallController(callSessionId, this);
|
|
@@ -297,6 +324,14 @@ export class CallController {
|
|
|
297
324
|
return this.state;
|
|
298
325
|
}
|
|
299
326
|
|
|
327
|
+
/**
|
|
328
|
+
* The call's per-turn latency marks so far. Read-only: the collector is fed
|
|
329
|
+
* from this controller's own turn lifecycle.
|
|
330
|
+
*/
|
|
331
|
+
getMetricsSnapshot(): VoiceMetricsSnapshot {
|
|
332
|
+
return this.metrics.getSnapshot();
|
|
333
|
+
}
|
|
334
|
+
|
|
300
335
|
/**
|
|
301
336
|
* Returns the question ID of the currently pending guardian consultation,
|
|
302
337
|
* or null if no consultation is active. Used by answerCall to match
|
|
@@ -370,6 +405,10 @@ export class CallController {
|
|
|
370
405
|
* consultation is pending — the consultation is tracked separately.
|
|
371
406
|
*/
|
|
372
407
|
async handleCallerUtterance(transcript: string): Promise<void> {
|
|
408
|
+
// Stamped before the teardown waits below, which can run for hundreds of
|
|
409
|
+
// milliseconds: the caller stopped talking now, not once the prior turn
|
|
410
|
+
// finished dying.
|
|
411
|
+
this.pendingFinalTranscriptAtMs = Date.now();
|
|
373
412
|
// If the caller speaks while an END_CALL teardown is pending (during the
|
|
374
413
|
// drain wait or the listen window), this is a deferral — the caller is
|
|
375
414
|
// re-engaging after we tried to hang up. Track it so we can cap repeats.
|
|
@@ -518,6 +557,7 @@ export class CallController {
|
|
|
518
557
|
{ callSessionId: this.callSessionId, state: this.state },
|
|
519
558
|
"Barge-in accepted: interrupting the assistant's turn",
|
|
520
559
|
);
|
|
560
|
+
this.markTurnMetric((turnId) => this.metrics.markBargeIn(turnId));
|
|
521
561
|
onAccepted?.();
|
|
522
562
|
this.handleInterrupt();
|
|
523
563
|
return true;
|
|
@@ -532,6 +572,9 @@ export class CallController {
|
|
|
532
572
|
* the transport's sustained-speech guard to vouch for the interruption.
|
|
533
573
|
*/
|
|
534
574
|
handleInterrupt(): void {
|
|
575
|
+
// One cancelled turn: the interruption ends the turn here, whether or not
|
|
576
|
+
// anything picks the work up afterwards.
|
|
577
|
+
this.cancelMetricsTurn("interrupted");
|
|
535
578
|
const wasSpeaking = this.state === "speaking";
|
|
536
579
|
this.abortCurrentTurn();
|
|
537
580
|
this.llmRunVersion++;
|
|
@@ -551,6 +594,7 @@ export class CallController {
|
|
|
551
594
|
*/
|
|
552
595
|
destroy(): void {
|
|
553
596
|
this.destroyed = true;
|
|
597
|
+
this.cancelMetricsTurn("call_ended");
|
|
554
598
|
if (this.silenceTimer) {
|
|
555
599
|
clearTimeout(this.silenceTimer);
|
|
556
600
|
}
|
|
@@ -658,6 +702,7 @@ export class CallController {
|
|
|
658
702
|
// Stamped before any pre-bridge work so the bridge's dispatch-timing log
|
|
659
703
|
// attributes the whole turn, TTS provider resolution included.
|
|
660
704
|
const launchedAtMs = Date.now();
|
|
705
|
+
this.openMetricsTurn(runVersion);
|
|
661
706
|
|
|
662
707
|
// Clear silence timer while actively processing. The caller said
|
|
663
708
|
// something (or a turn was triggered), so silence detection should
|
|
@@ -687,6 +732,10 @@ export class CallController {
|
|
|
687
732
|
await this.handleTurnCompletion(outcome);
|
|
688
733
|
} catch (err: unknown) {
|
|
689
734
|
this.currentTurnHandle = null;
|
|
735
|
+
// Only this run's turn: a turn whose abort outlives the teardown wait
|
|
736
|
+
// rejects after its replacement has already opened, and settling by
|
|
737
|
+
// "whatever is open" would cancel the healthy turn's marks.
|
|
738
|
+
this.cancelMetricsTurnForRun(runVersion, "turn_error");
|
|
690
739
|
// Aborted requests are expected (interruptions, rapid utterances)
|
|
691
740
|
if (this.isExpectedAbortError(err) || runSignal.aborted) {
|
|
692
741
|
log.debug(
|
|
@@ -1016,7 +1065,17 @@ export class CallController {
|
|
|
1016
1065
|
transcriptSoFar: () => callerTranscript,
|
|
1017
1066
|
language: () => this.resolveSynthesisLanguage(),
|
|
1018
1067
|
deltaEpoch: () => deltaEpoch,
|
|
1019
|
-
|
|
1068
|
+
// Only the cadence's own phrases count as narration; the escalation
|
|
1069
|
+
// bridge speaks through the same helper and is part of the answer.
|
|
1070
|
+
speak: (text, language) => {
|
|
1071
|
+
const spoken = speakFixedPhrase(text, language);
|
|
1072
|
+
if (spoken) {
|
|
1073
|
+
this.markTurnMetric((turnId) =>
|
|
1074
|
+
this.metrics.markProgressSpoken(turnId),
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
return spoken;
|
|
1078
|
+
},
|
|
1020
1079
|
},
|
|
1021
1080
|
});
|
|
1022
1081
|
const settleTurn = (): void => {
|
|
@@ -1149,6 +1208,13 @@ export class CallController {
|
|
|
1149
1208
|
flushSafeText(fullResponseText);
|
|
1150
1209
|
},
|
|
1151
1210
|
abortLeg: () => legHandle?.abort(),
|
|
1211
|
+
overruleLeg: () => {
|
|
1212
|
+
if (legHandle?.overrule) {
|
|
1213
|
+
legHandle.overrule();
|
|
1214
|
+
} else {
|
|
1215
|
+
legHandle?.abort();
|
|
1216
|
+
}
|
|
1217
|
+
},
|
|
1152
1218
|
speakBridge: speakEscalationBridge,
|
|
1153
1219
|
startEscalatedLeg: (escalated) => {
|
|
1154
1220
|
const started = runVoiceLeg(escalated);
|
|
@@ -1179,6 +1245,9 @@ export class CallController {
|
|
|
1179
1245
|
if (!this.isCurrentRun(runVersion) || coordinator?.handedOff) {
|
|
1180
1246
|
return;
|
|
1181
1247
|
}
|
|
1248
|
+
this.markTurnMetric((turnId) =>
|
|
1249
|
+
this.metrics.markFirstAssistantDelta(turnId),
|
|
1250
|
+
);
|
|
1182
1251
|
ingest(reasoningFilter.push(text));
|
|
1183
1252
|
};
|
|
1184
1253
|
|
|
@@ -1197,6 +1266,15 @@ export class CallController {
|
|
|
1197
1266
|
// A held "[..." tail that never completed a marker is real text,
|
|
1198
1267
|
// released to both consumers before the leg settles.
|
|
1199
1268
|
ingest(reasoningFilter.flush());
|
|
1269
|
+
// An answer held for the escalation judge is spoken or overruled
|
|
1270
|
+
// before the leg settles, so the turn awaits the right leg.
|
|
1271
|
+
if (coordinator?.awaitingJudge === true) {
|
|
1272
|
+
void coordinator.settled().then(() => {
|
|
1273
|
+
coordinator.complete();
|
|
1274
|
+
resolve();
|
|
1275
|
+
});
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1200
1278
|
coordinator?.complete();
|
|
1201
1279
|
resolve();
|
|
1202
1280
|
};
|
|
@@ -1205,6 +1283,11 @@ export class CallController {
|
|
|
1205
1283
|
reject(new Error(message));
|
|
1206
1284
|
};
|
|
1207
1285
|
|
|
1286
|
+
// First-wins across the turn's two legs, so this is the front-door
|
|
1287
|
+
// dispatch: the moment the caller's wait actually starts.
|
|
1288
|
+
this.markTurnMetric((turnId) =>
|
|
1289
|
+
this.metrics.markAssistantDispatch(turnId),
|
|
1290
|
+
);
|
|
1208
1291
|
startVoiceTurn({
|
|
1209
1292
|
conversationId: this.conversationId,
|
|
1210
1293
|
callSessionId: this.callSessionId,
|
|
@@ -1239,6 +1322,9 @@ export class CallController {
|
|
|
1239
1322
|
legHandle = handle;
|
|
1240
1323
|
if (this.isCurrentRun(runVersion) && !coordinator?.handedOff) {
|
|
1241
1324
|
this.currentTurnHandle = handle;
|
|
1325
|
+
if (coordinator !== null && handle.escalationJudgement) {
|
|
1326
|
+
coordinator.attachEscalationJudge(handle.escalationJudgement);
|
|
1327
|
+
}
|
|
1242
1328
|
} else {
|
|
1243
1329
|
// Superseded, or the front-door leg handed off before its
|
|
1244
1330
|
// handle arrived: abort immediately.
|
|
@@ -1494,6 +1580,7 @@ export class CallController {
|
|
|
1494
1580
|
* and normal idle transition.
|
|
1495
1581
|
*/
|
|
1496
1582
|
private async handleTurnCompletion(outcome: VoiceTurnOutcome): Promise<void> {
|
|
1583
|
+
this.completeMetricsTurn();
|
|
1497
1584
|
const responseText = outcome.text;
|
|
1498
1585
|
|
|
1499
1586
|
// Record the assistant response event, keyed to the conversation rows it
|
|
@@ -1923,6 +2010,10 @@ export class CallController {
|
|
|
1923
2010
|
if (this.state === "processing") {
|
|
1924
2011
|
this.state = "speaking";
|
|
1925
2012
|
}
|
|
2013
|
+
// Real outbound audio, not merely buffered tokens: on media-stream this
|
|
2014
|
+
// fires from the transport's audio-start signal, which is the closest the
|
|
2015
|
+
// daemon gets to the moment the caller hears the answer.
|
|
2016
|
+
this.markTurnMetric((turnId) => this.metrics.markFirstTtsAudio(turnId));
|
|
1926
2017
|
}
|
|
1927
2018
|
|
|
1928
2019
|
/**
|
|
@@ -1947,6 +2038,110 @@ export class CallController {
|
|
|
1947
2038
|
}
|
|
1948
2039
|
}
|
|
1949
2040
|
|
|
2041
|
+
/**
|
|
2042
|
+
* Open the collector's turn for this run, seeded with the caller's
|
|
2043
|
+
* transcript mark.
|
|
2044
|
+
*
|
|
2045
|
+
* A turn still open here was superseded by a rapid follow-up utterance
|
|
2046
|
+
* (which aborts the old turn without going through the interrupt path), so
|
|
2047
|
+
* it settles as cancelled and gets its line in the log before the new one
|
|
2048
|
+
* starts.
|
|
2049
|
+
*/
|
|
2050
|
+
private openMetricsTurn(runVersion: number): void {
|
|
2051
|
+
this.cancelMetricsTurn("superseded");
|
|
2052
|
+
const turnId = this.metricsTurnIdForRun(runVersion);
|
|
2053
|
+
const finalTranscriptAtMs = this.pendingFinalTranscriptAtMs;
|
|
2054
|
+
this.pendingFinalTranscriptAtMs = null;
|
|
2055
|
+
this.metricsTurnId = turnId;
|
|
2056
|
+
// The provider's utterance-boundary final is both the transcript and the
|
|
2057
|
+
// moment the caller stopped speaking, so it anchors the round trip too;
|
|
2058
|
+
// a phone turn has no separate VAD end or push-to-talk release to use.
|
|
2059
|
+
this.metrics.startTurn(
|
|
2060
|
+
turnId,
|
|
2061
|
+
finalTranscriptAtMs !== null
|
|
2062
|
+
? { finalTranscriptAtMs, utteranceEndAtMs: finalTranscriptAtMs }
|
|
2063
|
+
: {},
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
/**
|
|
2068
|
+
* Run a mark against the call's open turn, if there is one.
|
|
2069
|
+
*
|
|
2070
|
+
* The collector opens a turn for any mark that arrives without one, so
|
|
2071
|
+
* every phone mark is guarded: a delta from a superseded leg or a barge-in
|
|
2072
|
+
* between turns must leave no trace rather than invent a turn that never
|
|
2073
|
+
* settles.
|
|
2074
|
+
*/
|
|
2075
|
+
private markTurnMetric(mark: (turnId: string) => void): void {
|
|
2076
|
+
if (this.metricsTurnId === null) {
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
mark(this.metricsTurnId);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
private completeMetricsTurn(): void {
|
|
2083
|
+
this.settleMetricsTurn("completed", (turnId) =>
|
|
2084
|
+
this.metrics.completeTurn(turnId),
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
private cancelMetricsTurn(reason: string): void {
|
|
2089
|
+
this.settleMetricsTurn(reason, (turnId) =>
|
|
2090
|
+
this.metrics.cancelTurn(reason, turnId),
|
|
2091
|
+
);
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
/** The turn id a given run's marks belong to. */
|
|
2095
|
+
private metricsTurnIdForRun(runVersion: number): string {
|
|
2096
|
+
return `${this.callSessionId}#${runVersion}`;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
/**
|
|
2100
|
+
* Cancel the open turn only if it is the one this run opened.
|
|
2101
|
+
*
|
|
2102
|
+
* Defensive: the abort listener resolves a superseded leg rather than
|
|
2103
|
+
* rejecting it, and a genuine rejection is processed before the replacement
|
|
2104
|
+
* turn opens, so no path today reaches the error handler holding a newer
|
|
2105
|
+
* turn. Scoping the settle to its own run keeps that true by construction
|
|
2106
|
+
* rather than by the ordering of two other code paths.
|
|
2107
|
+
*/
|
|
2108
|
+
private cancelMetricsTurnForRun(runVersion: number, reason: string): void {
|
|
2109
|
+
if (this.metricsTurnId !== this.metricsTurnIdForRun(runVersion)) {
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
this.cancelMetricsTurn(reason);
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
/**
|
|
2116
|
+
* Settle the open turn and log what it measured.
|
|
2117
|
+
*
|
|
2118
|
+
* Live voice streams a metrics frame to its client on every mark; a call
|
|
2119
|
+
* has no client on the line, so the per-turn aggregate lands in the log
|
|
2120
|
+
* instead. Cancelled turns are logged too: a turn the caller cut off still
|
|
2121
|
+
* measured everything up to the interruption, and how long the assistant
|
|
2122
|
+
* took before being cut off is the more interesting number of the two.
|
|
2123
|
+
*/
|
|
2124
|
+
private settleMetricsTurn(
|
|
2125
|
+
status: string,
|
|
2126
|
+
settle: (turnId: string) => void,
|
|
2127
|
+
): void {
|
|
2128
|
+
const turnId = this.metricsTurnId;
|
|
2129
|
+
if (turnId === null) {
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
this.metricsTurnId = null;
|
|
2133
|
+
settle(turnId);
|
|
2134
|
+
log.info(
|
|
2135
|
+
{
|
|
2136
|
+
callSessionId: this.callSessionId,
|
|
2137
|
+
turnId,
|
|
2138
|
+
status,
|
|
2139
|
+
...getVoiceMetricsAggregateFields(this.metrics.getSnapshot(), turnId),
|
|
2140
|
+
},
|
|
2141
|
+
"Phone call turn metrics",
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
|
|
1950
2145
|
private isCurrentRun(runVersion: number): boolean {
|
|
1951
2146
|
return runVersion === this.llmRunVersion;
|
|
1952
2147
|
}
|
package/src/calls/call-domain.ts
CHANGED
|
@@ -299,6 +299,7 @@ export async function createInboundVoiceSession(
|
|
|
299
299
|
const session = createCallSession({
|
|
300
300
|
conversationId: voiceConversationId,
|
|
301
301
|
provider: "twilio",
|
|
302
|
+
direction: "inbound",
|
|
302
303
|
fromNumber,
|
|
303
304
|
toNumber,
|
|
304
305
|
});
|
|
@@ -438,6 +439,7 @@ export async function startCall(
|
|
|
438
439
|
signal?.throwIfAborted();
|
|
439
440
|
|
|
440
441
|
const session = createCallSession({
|
|
442
|
+
direction: "outbound",
|
|
441
443
|
conversationId,
|
|
442
444
|
provider: "twilio",
|
|
443
445
|
fromNumber,
|
|
@@ -976,6 +978,7 @@ export async function startVerificationCall(
|
|
|
976
978
|
});
|
|
977
979
|
|
|
978
980
|
const session = createCallSession({
|
|
981
|
+
direction: "outbound",
|
|
979
982
|
conversationId,
|
|
980
983
|
provider: "twilio",
|
|
981
984
|
fromNumber: identityResult.fromNumber,
|
|
@@ -1109,6 +1112,7 @@ export async function startInviteCall(
|
|
|
1109
1112
|
});
|
|
1110
1113
|
|
|
1111
1114
|
const session = createCallSession({
|
|
1115
|
+
direction: "outbound",
|
|
1112
1116
|
conversationId,
|
|
1113
1117
|
provider: "twilio",
|
|
1114
1118
|
fromNumber: identityResult.fromNumber,
|
package/src/calls/call-store.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { and, desc, eq, notInArray, or } from "drizzle-orm";
|
|
1
|
+
import { and, desc, eq, like, notInArray, or } from "drizzle-orm";
|
|
2
2
|
import { v4 as uuid } from "uuid";
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
recordPhoneCallEnded,
|
|
6
|
+
recordPhoneCallStarted,
|
|
7
|
+
} from "../onboarding/onboarding-events-store.js";
|
|
4
8
|
import { getDb } from "../persistence/db-connection.js";
|
|
5
9
|
import { rawChanges, rawRun } from "../persistence/raw-query.js";
|
|
6
10
|
import {
|
|
@@ -8,10 +12,16 @@ import {
|
|
|
8
12
|
callPendingQuestions,
|
|
9
13
|
callSessions,
|
|
10
14
|
} from "../persistence/schema/index.js";
|
|
15
|
+
import {
|
|
16
|
+
type PhoneCallDirection,
|
|
17
|
+
phoneCallEndScreen,
|
|
18
|
+
phoneCallSilenceReason,
|
|
19
|
+
phoneCallStartScreen,
|
|
20
|
+
} from "../telemetry/phone-call-funnel.js";
|
|
11
21
|
import { getLogger } from "../util/logger.js";
|
|
12
22
|
import { cast, createRowMapper } from "../util/row-mapper.js";
|
|
13
23
|
import { syncActiveCallLeaseFromSession } from "./active-call-lease.js";
|
|
14
|
-
import { validateTransition } from "./call-state-machine.js";
|
|
24
|
+
import { isTerminalState, validateTransition } from "./call-state-machine.js";
|
|
15
25
|
import type {
|
|
16
26
|
CallEvent,
|
|
17
27
|
CallEventType,
|
|
@@ -85,6 +95,12 @@ export function createCallSession(opts: {
|
|
|
85
95
|
provider: string;
|
|
86
96
|
fromNumber: string;
|
|
87
97
|
toNumber: string;
|
|
98
|
+
/**
|
|
99
|
+
* Which way the call is being placed, for the funnel stamp. Stated rather
|
|
100
|
+
* than derived: a verification or invite call carries no task and is still
|
|
101
|
+
* outbound, so task presence is not a direction.
|
|
102
|
+
*/
|
|
103
|
+
direction: PhoneCallDirection;
|
|
88
104
|
task?: string;
|
|
89
105
|
callMode?: string;
|
|
90
106
|
verificationSessionId?: string;
|
|
@@ -122,6 +138,14 @@ export function createCallSession(opts: {
|
|
|
122
138
|
updatedAt: now,
|
|
123
139
|
};
|
|
124
140
|
db.insert(callSessions).values(row).run();
|
|
141
|
+
// The call is attempted the moment its row exists, which is before the
|
|
142
|
+
// provider dials: a call that never connects is exactly the one the funnel
|
|
143
|
+
// needs to count. An outbound attempt the ingress preflight rejects never
|
|
144
|
+
// reaches here; see `telemetry/phone-call-funnel.ts`.
|
|
145
|
+
recordPhoneCallStarted({
|
|
146
|
+
callSessionId: row.id,
|
|
147
|
+
screen: phoneCallStartScreen(opts.direction, row.callMode),
|
|
148
|
+
});
|
|
125
149
|
return { ...row, skipDisclosure };
|
|
126
150
|
}
|
|
127
151
|
|
|
@@ -224,6 +248,13 @@ export function updateCallSession(
|
|
|
224
248
|
.where(eq(callSessions.id, id))
|
|
225
249
|
.run();
|
|
226
250
|
|
|
251
|
+
// Terminal states are immutable and the validator above rejects any write
|
|
252
|
+
// that follows one, so the first terminal transition to get here is the only
|
|
253
|
+
// one: the ended event needs no separate once-only latch.
|
|
254
|
+
if (updates.status && isTerminalState(updates.status)) {
|
|
255
|
+
recordCallEndedEvent(id, updates.status);
|
|
256
|
+
}
|
|
257
|
+
|
|
227
258
|
opts?.beforeLeaseSync?.();
|
|
228
259
|
|
|
229
260
|
if (shouldSyncActiveLease) {
|
|
@@ -240,6 +271,60 @@ export function updateCallSession(
|
|
|
240
271
|
|
|
241
272
|
// ── Recovery queries ─────────────────────────────────────────────────
|
|
242
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Whether the call's own event log holds an event of this type. Existence
|
|
276
|
+
* only: the row itself is never read.
|
|
277
|
+
*/
|
|
278
|
+
function hasCallEvent(
|
|
279
|
+
id: string,
|
|
280
|
+
eventType: CallEventType,
|
|
281
|
+
payloadContains?: string,
|
|
282
|
+
): boolean {
|
|
283
|
+
const db = getDb();
|
|
284
|
+
const row = db
|
|
285
|
+
.select({ id: callEvents.id })
|
|
286
|
+
.from(callEvents)
|
|
287
|
+
.where(
|
|
288
|
+
and(
|
|
289
|
+
eq(callEvents.callSessionId, id),
|
|
290
|
+
eq(callEvents.eventType, eventType),
|
|
291
|
+
...(payloadContains
|
|
292
|
+
? [like(callEvents.payloadJson, `%${payloadContains}%`)]
|
|
293
|
+
: []),
|
|
294
|
+
),
|
|
295
|
+
)
|
|
296
|
+
.limit(1)
|
|
297
|
+
.get();
|
|
298
|
+
return row !== undefined;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Emit the call's funnel end event, classifying a call that never took a
|
|
303
|
+
* caller turn by how far it got. Both signals come from the call's own event
|
|
304
|
+
* log: `call_connected` is written when the media stream opens, and
|
|
305
|
+
* `caller_spoke` when the caller is heard.
|
|
306
|
+
*
|
|
307
|
+
* `caller_spoke` carries two senses, though: a transcribed utterance
|
|
308
|
+
* (`transcript`) and a single DTMF digit (`dtmfDigit`). Only the first is a
|
|
309
|
+
* conversational turn, so the payload discriminates them. Splitting DTMF into
|
|
310
|
+
* an event type of its own would be the real fix, but that vocabulary is read
|
|
311
|
+
* by other consumers and is not this change's to alter.
|
|
312
|
+
*/
|
|
313
|
+
function recordCallEndedEvent(id: string, status: CallStatus): void {
|
|
314
|
+
const spoke = hasCallEvent(id, "caller_spoke", '"transcript"');
|
|
315
|
+
const silence = spoke
|
|
316
|
+
? null
|
|
317
|
+
: phoneCallSilenceReason({
|
|
318
|
+
connected: hasCallEvent(id, "call_connected"),
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
recordPhoneCallEnded({
|
|
322
|
+
callSessionId: id,
|
|
323
|
+
screen: phoneCallEndScreen(status, silence),
|
|
324
|
+
outcome: status === "failed" ? "failed" : "completed",
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
243
328
|
/**
|
|
244
329
|
* Returns all call sessions that are in a non-terminal state
|
|
245
330
|
* (i.e. not completed, failed, or cancelled). Used during daemon startup
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
* Guardian action expiry notices.
|
|
3
3
|
*
|
|
4
4
|
* Sends "this request has expired" notices to guardian delivery
|
|
5
|
-
* destinations
|
|
6
|
-
* channels
|
|
5
|
+
* destinations: vellum conversations get an assistant message, and external
|
|
6
|
+
* channels get a direct channel reply on the route
|
|
7
|
+
* `resolveDeliverCallbackUrlForChannel` names for the channel.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import { DELIVERY_STATUS } from "@vellumai/gateway-client";
|
|
10
11
|
|
|
12
|
+
import { resolveDeliverCallbackUrlForChannel } from "../approvals/guardian-channel-delivery.js";
|
|
11
13
|
import { addMessage } from "../persistence/conversation-crud.js";
|
|
12
14
|
import { deliverChannelReply } from "../runtime/gateway-client.js";
|
|
13
|
-
import {
|
|
15
|
+
import { composeGuardianActionMessage } from "../runtime/guardian-action-message-composer.js";
|
|
14
16
|
import { getLogger } from "../util/logger.js";
|
|
15
17
|
|
|
16
18
|
const log = getLogger("guardian-action-sweep");
|
|
@@ -27,7 +29,12 @@ export interface ExpiryDeliveryInfo {
|
|
|
27
29
|
/**
|
|
28
30
|
* Send expiry notices to all delivery destinations for a guardian action
|
|
29
31
|
* request. Handles both vellum/mac conversation messages and external channel
|
|
30
|
-
* replies
|
|
32
|
+
* replies.
|
|
33
|
+
*
|
|
34
|
+
* A channel delivery's `destinationChatId` is the notification endpoint the
|
|
35
|
+
* card went to. On Discord that is the guardian's user snowflake, so the
|
|
36
|
+
* notice must take the `dm`-marked route that opens their DM; posted as a
|
|
37
|
+
* channel id it would address nothing.
|
|
31
38
|
*
|
|
32
39
|
* Deliveries must be captured *before* their status is changed to 'expired'
|
|
33
40
|
* so the sent/pending filter still matches.
|
|
@@ -45,9 +52,8 @@ export async function sendGuardianExpiryNotices(
|
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
try {
|
|
48
|
-
const expiryText =
|
|
55
|
+
const expiryText = composeGuardianActionMessage({
|
|
49
56
|
scenario: "guardian_stale_expired",
|
|
50
|
-
channel: delivery.destinationChannel,
|
|
51
57
|
});
|
|
52
58
|
|
|
53
59
|
if (
|
|
@@ -69,8 +75,16 @@ export async function sendGuardianExpiryNotices(
|
|
|
69
75
|
},
|
|
70
76
|
);
|
|
71
77
|
} else if (delivery.destinationChatId) {
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
const deliverUrl = resolveDeliverCallbackUrlForChannel(
|
|
79
|
+
delivery.destinationChannel,
|
|
80
|
+
);
|
|
81
|
+
if (!deliverUrl) {
|
|
82
|
+
log.warn(
|
|
83
|
+
{ deliveryId: delivery.id, channel: delivery.destinationChannel },
|
|
84
|
+
"No delivery route for guardian expiry notice channel",
|
|
85
|
+
);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
74
88
|
await deliverChannelReply(deliverUrl, {
|
|
75
89
|
chatId: delivery.destinationChatId,
|
|
76
90
|
text: expiryText,
|
|
@@ -68,9 +68,7 @@ function linearToMulaw(sample: number): number {
|
|
|
68
68
|
// Determine segment
|
|
69
69
|
const exponent = segmentSearch(sample);
|
|
70
70
|
const mantissa = (sample >> (exponent + 3)) & 0x0f;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return mulawByte;
|
|
71
|
+
return ~(sign | (exponent << 4) | mantissa) & 0xff;
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
function segmentSearch(val: number): number {
|