@vellumai/assistant 0.12.1-staging.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +0 -4
- package/ARCHITECTURE.md +12 -11
- package/docs/architecture/turn-actor.md +8 -15
- package/openapi.yaml +1 -291
- package/package.json +1 -1
- package/src/__tests__/always-loaded-tools-guard.test.ts +10 -18
- package/src/__tests__/app-compiler.test.ts +0 -6
- package/src/__tests__/app-control-tool-schemas.test.ts +2 -5
- package/src/__tests__/app-dir-path-guard.test.ts +3 -1
- package/src/__tests__/app-source-watcher.test.ts +218 -0
- package/src/{calls/__tests__ → __tests__}/call-controller.test.ts +82 -503
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +2 -5
- package/src/__tests__/computer-use-tools.test.ts +5 -17
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +29 -28
- package/src/__tests__/config-watcher-skill-reseed.test.ts +359 -0
- package/src/__tests__/config-watcher.test.ts +8 -23
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +0 -26
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -4
- package/src/__tests__/conversation-queue.test.ts +207 -48
- package/src/__tests__/conversation-retry-discard.test.ts +1 -54
- package/src/__tests__/conversation-skill-tools.test.ts +0 -27
- package/src/__tests__/conversation-surfaces-app-control.test.ts +3 -8
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -67
- package/src/__tests__/conversation-title-service.test.ts +10 -14
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +5 -3
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -51
- package/src/__tests__/credential-record-retire.test.ts +1 -171
- package/src/__tests__/credential-routes.test.ts +3 -212
- package/src/__tests__/credential-security-invariants.test.ts +1 -2
- package/src/__tests__/cu-unified-flow.test.ts +2 -8
- package/src/__tests__/drain-kick-guard.test.ts +2 -0
- package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/host-bash-proxy.test.ts +45 -26
- package/src/__tests__/host-browser-proxy.test.ts +17 -6
- package/src/__tests__/host-file-proxy-targeted.test.ts +0 -23
- package/src/__tests__/host-file-proxy.test.ts +23 -11
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +9 -30
- package/src/__tests__/llm-catalog-parity.test.ts +0 -25
- package/src/__tests__/mcp-auth-routes.test.ts +9 -6
- package/src/__tests__/mcp-config-secret-boundary.test.ts +136 -78
- package/src/__tests__/mcp-health-check.test.ts +11 -8
- package/src/__tests__/mcp-list-plugin-servers.test.ts +10 -8
- package/src/{calls/__tests__ → __tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-output.test.ts +8 -37
- package/src/{calls/__tests__ → __tests__}/media-stream-parser.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-server-integration.test.ts +57 -150
- package/src/{calls/__tests__ → __tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{calls/__tests__ → __tests__}/media-turn-detector.test.ts +1 -1
- package/src/__tests__/messages-after-tiebreaker.test.ts +0 -91
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +548 -355
- package/src/__tests__/model-intents.test.ts +0 -3
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-client-verify-credential.test.ts +0 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -13
- package/src/__tests__/script-proxy-certs.test.ts +0 -35
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/speaker-identification.test.ts +56 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +3 -19
- package/src/__tests__/subagent-tools.test.ts +2 -3
- package/src/__tests__/telephony-tts-guard.test.ts +3 -38
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -4
- package/src/{calls/__tests__/voice-session-bridge-turn-flow.test.ts → __tests__/voice-session-bridge.test.ts} +12 -20
- package/src/__tests__/watch-retro-tool-availability.test.ts +17 -8
- package/src/__tests__/workspace-git-service.test.ts +0 -26
- package/src/acp/__tests__/agent-process.test.ts +4 -258
- package/src/acp/__tests__/auth-required.test.ts +0 -96
- package/src/acp/__tests__/client-handler.test.ts +2 -90
- package/src/acp/__tests__/session-manager-persistence.test.ts +1 -5
- package/src/acp/__tests__/session-manager-resume.test.ts +6 -270
- package/src/acp/__tests__/session-manager-usage.test.ts +2 -4
- package/src/acp/__tests__/session-manager.test.ts +4 -895
- package/src/acp/agent-process.test.ts +1 -4
- package/src/acp/agent-process.ts +9 -94
- package/src/acp/auth-required.ts +1 -50
- package/src/acp/auto-install.test.ts +7 -9
- package/src/acp/client-handler.ts +3 -16
- package/src/acp/resolve-agent.test.ts +6 -220
- package/src/acp/resolve-agent.ts +23 -88
- package/src/acp/session-manager.test.ts +3 -196
- package/src/acp/session-manager.ts +18 -441
- package/src/acp/types.ts +5 -50
- package/src/api/events/app-files-changed.ts +3 -4
- package/src/api/index.ts +0 -6
- package/src/apps/app-store.ts +1 -1
- package/src/bundler/app-compiler.ts +0 -15
- package/src/calls/__tests__/voice-control-protocol.test.ts +25 -58
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -37
- package/src/calls/__tests__/voice-triage-escalate.test.ts +0 -105
- package/src/calls/call-constants.ts +4 -24
- package/src/calls/call-controller.ts +199 -499
- package/src/calls/call-transport.ts +0 -16
- package/src/calls/media-stream-output.ts +0 -32
- package/src/calls/media-stream-server.ts +30 -82
- package/src/calls/media-stream-stt-session.ts +0 -15
- package/src/calls/resolve-call-tts-provider.ts +2 -33
- package/src/calls/speaker-identification.ts +233 -0
- package/src/calls/tts-call-strategy.ts +5 -9
- package/src/calls/types.ts +0 -2
- package/src/calls/voice-control-protocol.ts +21 -53
- package/src/calls/voice-session-bridge.ts +46 -59
- package/src/calls/voice-triage-escalate.ts +56 -175
- package/src/cli/commands/apps.help.ts +13 -67
- package/src/cli/commands/apps.ts +0 -183
- package/src/cli/commands/channels/index.help.ts +2 -70
- package/src/cli/commands/channels/index.ts +3 -16
- package/src/cli/commands/credentials.help.ts +2 -6
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/roadmap.help.ts +1 -5
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +90 -103
- package/src/config/__tests__/default-profile-catalog.test.ts +0 -22
- package/src/config/__tests__/memory-retrospective-schema.test.ts +1 -15
- package/src/config/__tests__/platform-rehydration.test.ts +0 -93
- package/src/config/acp-defaults.test.ts +3 -13
- package/src/config/acp-defaults.ts +6 -23
- package/src/config/acp-schema.ts +2 -30
- package/src/config/bundled-skills/acp/SKILL.md +14 -22
- package/src/config/bundled-skills/acp/TOOLS.json +1 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +9 -12
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +139 -67
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +1 -8
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +1 -4
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +12 -8
- package/src/config/memory-v3-gate.test.ts +0 -31
- package/src/config/memory-v3-gate.ts +16 -20
- package/src/config/platform-rehydration.ts +21 -75
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +158 -8
- package/src/config/schemas/calls.ts +28 -1
- package/src/config/schemas/live-voice.ts +173 -1
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +0 -10
- package/src/config/schemas/skills.ts +14 -0
- package/src/credential-execution/process-manager.test.ts +19 -18
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +121 -66
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -4
- package/src/daemon/app-source-watcher.ts +218 -0
- package/src/daemon/config-watcher.ts +194 -13
- package/src/daemon/conversation-process.ts +64 -35
- package/src/daemon/conversation-skill-tools.ts +31 -13
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +8 -10
- package/src/daemon/conversation-tool-setup.ts +43 -37
- package/src/daemon/conversation.ts +3 -0
- package/src/daemon/host-bash-proxy.ts +12 -8
- package/src/daemon/host-browser-proxy.ts +1 -15
- package/src/daemon/host-file-proxy.ts +10 -8
- package/src/daemon/host-transfer-proxy.ts +18 -14
- package/src/daemon/lifecycle.ts +5 -0
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/shutdown-handlers.ts +9 -1
- package/src/daemon/skill-memory-refresh.ts +0 -64
- package/src/daemon/tool-side-effects.ts +18 -7
- package/src/hooks/types.ts +0 -33
- package/src/inbound/platform-callback-registration.ts +5 -4
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +3 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/{calls → live-voice}/__tests__/progress-narration.test.ts +4 -4
- package/src/{calls → live-voice}/__tests__/progress-phrases.test.ts +1 -1
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +69 -13
- package/src/live-voice/live-voice-session.ts +693 -213
- package/src/{calls → live-voice}/progress-narration.ts +2 -2
- package/src/live-voice/protocol.ts +2 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +45 -29
- package/src/mcp/startup.ts +4 -8
- package/src/messaging/provider.ts +3 -10
- package/src/messaging/providers/callback-routing.ts +0 -18
- package/src/messaging/providers/channel-transport.ts +0 -56
- package/src/messaging/providers/discord/transport.ts +0 -17
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +17 -6
- package/src/messaging/providers/slack/adapter.ts +44 -7
- package/src/messaging/providers/slack/transport.ts +0 -16
- package/src/messaging/providers/telegram-bot/adapter.ts +48 -1
- package/src/messaging/providers/telegram-bot/client.ts +26 -0
- package/src/messaging/providers/telegram-bot/transport.ts +0 -20
- package/src/messaging/providers/whatsapp/adapter.ts +48 -1
- package/src/messaging/providers/whatsapp/client.ts +29 -0
- package/src/messaging/providers/whatsapp/transport.ts +0 -18
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +0 -8
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +0 -55
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +3 -153
- package/src/notifications/assistant-reply-producer.ts +3 -8
- package/src/notifications/conversation-pairing.ts +16 -36
- package/src/notifications/delivered-post-record.ts +1 -11
- package/src/notifications/home-feed-side-effect.ts +7 -55
- package/src/outbound-proxy/certs.ts +5 -3
- package/src/permissions/checker.test.ts +6 -11
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +63 -71
- package/src/persistence/conversation-lineage.ts +4 -12
- package/src/persistence/conversation-title-service.ts +26 -9
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-reads.ts +0 -30
- package/src/persistence/migrations/157-invite-contact-id.ts +6 -18
- package/src/persistence/schema/memory-core.ts +0 -5
- package/src/platform/client.test.ts +0 -2
- package/src/platform/client.ts +10 -2
- package/src/plugin-api/constants.ts +0 -2
- package/src/plugin-api/index.ts +0 -1
- package/src/plugin-api/types.ts +0 -2
- package/src/plugins/defaults/index.ts +0 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +0 -46
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +5 -9
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +10 -20
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +0 -219
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +0 -31
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -63
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +0 -4
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -4
- package/src/plugins/defaults/memory/hooks/init.ts +0 -10
- package/src/plugins/defaults/memory/hooks/shutdown.ts +0 -2
- package/src/plugins/defaults/memory/jobs-worker.ts +0 -13
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +60 -34
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +5 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -12
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +71 -312
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +3 -5
- package/src/plugins/defaults/memory/startup.ts +1 -6
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +9 -7
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +0 -89
- package/src/plugins/defaults/memory/worker.ts +0 -6
- package/src/plugins/mcp-servers.ts +75 -15
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/gemini/client.ts +23 -9
- package/src/providers/model-catalog.ts +0 -34
- package/src/providers/model-intents.ts +5 -10
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +18 -146
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +1 -83
- package/src/providers/openai/chat-completions-provider.ts +78 -192
- package/src/providers/openai/codex-models.ts +4 -0
- package/src/runtime/AGENTS.md +0 -2
- package/src/runtime/auth/same-actor.ts +3 -38
- package/src/runtime/http-server.ts +151 -160
- package/src/runtime/routes/__tests__/acp-routes.test.ts +4 -159
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -108
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +12 -16
- package/src/runtime/routes/acp-routes.test.ts +0 -15
- package/src/runtime/routes/acp-routes.ts +9 -72
- package/src/runtime/routes/app-management-routes.ts +0 -73
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +3 -14
- package/src/runtime/routes/conversation-query-routes.ts +0 -40
- package/src/runtime/routes/credential-routes.ts +10 -19
- package/src/runtime/routes/diagnostics-routes.ts +3 -55
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/log-export-routes.ts +0 -2
- package/src/runtime/routes/mcp-auth-routes.ts +47 -39
- package/src/runtime/routes/migration-routes.ts +21 -8
- package/src/runtime/routes/platform-routes.ts +15 -18
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -28
- package/src/runtime/services/conversation-serializer.ts +3 -16
- package/src/runtime/sync/resource-sync-events.ts +1 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/ces-rpc-record-backend.ts +0 -7
- package/src/subagent/consult-context.ts +25 -12
- package/src/tools/acp/list-agents.test.ts +1 -3
- package/src/tools/acp/spawn.test.ts +11 -146
- package/src/tools/acp/spawn.ts +3 -21
- package/src/tools/acp/status.ts +3 -40
- package/src/tools/app-control/skill-proxy-bridge.ts +1 -2
- package/src/tools/capability-offer.test.ts +2 -22
- package/src/tools/capability-offer.ts +1 -16
- package/src/tools/computer-use/definitions.ts +2 -4
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +0 -100
- package/src/tools/host-filesystem/edit.test.ts +1 -2
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +1 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +1 -2
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +1 -2
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +0 -16
- package/src/tools/host-terminal/host-shell.ts +20 -4
- package/src/tools/policy-context.ts +7 -6
- package/src/tools/subagent/spawn.ts +8 -5
- package/src/tools/watch/watch-retro-report.ts +8 -0
- package/src/util/__tests__/language-subtag.test.ts +1 -23
- package/src/util/fs-watcher-error.ts +36 -0
- package/src/util/language-subtag.ts +0 -16
- package/src/util/platform.ts +0 -8
- package/src/workspace/git-service.ts +54 -5
- package/src/workspace/migrations/registry.ts +0 -6
- package/scripts/smoke-desktop-window-theme.ts +0 -186
- package/src/__tests__/git-porcelain.test.ts +0 -47
- package/src/__tests__/helpers/set-workspace-mcp.ts +0 -11
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +0 -119
- package/src/__tests__/resolve-app.test.ts +0 -135
- package/src/__tests__/source-fingerprint.test.ts +0 -184
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +0 -336
- package/src/acp/__tests__/helpers/acp-model-option.ts +0 -51
- package/src/acp/model-config.test.ts +0 -166
- package/src/acp/model-config.ts +0 -138
- package/src/api/events/acp-session-model-update.test.ts +0 -88
- package/src/api/events/acp-session-model-update.ts +0 -46
- package/src/apps/resolve-app.ts +0 -81
- package/src/apps/source-fingerprint.ts +0 -266
- package/src/calls/__tests__/barge-in-guard.test.ts +0 -81
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +0 -229
- package/src/calls/__tests__/voice-progress-cadence.test.ts +0 -274
- package/src/calls/barge-in-guard.ts +0 -118
- package/src/calls/voice-leg-coordinator.ts +0 -189
- package/src/calls/voice-progress-cadence.ts +0 -423
- package/src/cli/commands/__tests__/apps.test.ts +0 -245
- package/src/cli/commands/__tests__/roadmap.help.test.ts +0 -19
- package/src/cli/commands/channels/__tests__/send.test.ts +0 -218
- package/src/cli/commands/channels/send.ts +0 -128
- package/src/config/__tests__/platform-identity.test.ts +0 -117
- package/src/config/acp-agent-write.ts +0 -43
- package/src/config/acp-schema.test.ts +0 -81
- package/src/config/platform-identity.ts +0 -144
- package/src/config/schemas/__tests__/voice.test.ts +0 -148
- package/src/config/schemas/voice.ts +0 -150
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +0 -133
- package/src/daemon/app-change-notify.ts +0 -45
- package/src/desktop/__tests__/fake-desktop.ts +0 -119
- package/src/desktop/desktop-chrome-policy.test.ts +0 -71
- package/src/desktop/desktop-chrome-policy.ts +0 -42
- package/src/desktop/desktop-dependencies.test.ts +0 -102
- package/src/desktop/desktop-dependencies.ts +0 -306
- package/src/desktop/desktop-feature.test.ts +0 -18
- package/src/desktop/desktop-feature.ts +0 -19
- package/src/desktop/desktop-panel-config.test.ts +0 -135
- package/src/desktop/desktop-panel-config.ts +0 -149
- package/src/desktop/desktop-session-manager.test.ts +0 -627
- package/src/desktop/desktop-session-manager.ts +0 -814
- package/src/desktop/desktop-stream-bridge.test.ts +0 -395
- package/src/desktop/desktop-stream-bridge.ts +0 -214
- package/src/desktop/desktop-wallpaper.test.ts +0 -191
- package/src/desktop/desktop-wallpaper.ts +0 -113
- package/src/desktop/desktop-window-theme.ts +0 -170
- package/src/i18n/AGENTS.md +0 -22
- package/src/i18n/__tests__/i18n.test.ts +0 -108
- package/src/i18n/index.ts +0 -16
- package/src/i18n/locales.ts +0 -93
- package/src/i18n/messages.ts +0 -61
- package/src/i18n/title.ts +0 -25
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +0 -135
- package/src/mcp/spec-schema.ts +0 -111
- package/src/mcp/workspace-mcp-config.ts +0 -188
- package/src/messaging/providers/__tests__/proactive-address.test.ts +0 -95
- package/src/persistence/conversation-title-placeholders.ts +0 -23
- package/src/persistence/message-cursor.ts +0 -78
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +0 -149
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +0 -118
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +0 -83
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +0 -21
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +0 -126
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +0 -79
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +0 -28
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +0 -9
- package/src/providers/gemini-thought-signature.test.ts +0 -63
- package/src/providers/gemini-thought-signature.ts +0 -44
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +0 -613
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +0 -235
- package/src/providers/openai/google-thought-signature.ts +0 -320
- package/src/runtime/__tests__/channel-send.test.ts +0 -443
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +0 -89
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +0 -58
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +0 -72
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +0 -59
- package/src/runtime/channel-send.ts +0 -316
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +0 -205
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +0 -127
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
- package/src/runtime/routes/channel-send-routes.ts +0 -85
- package/src/runtime/routes/desktop-setup-routes.test.ts +0 -74
- package/src/runtime/routes/desktop-setup-routes.ts +0 -35
- package/src/security/__tests__/ces-http-record-backend.test.ts +0 -137
- package/src/security/ces-http-record-backend.ts +0 -120
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +0 -60
- package/src/skills/workspace-skill-md-signature.ts +0 -50
- package/src/tools/acp/status.test.ts +0 -94
- package/src/util/ca-bundle.ts +0 -13
- package/src/workspace/git-porcelain.ts +0 -66
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +0 -258
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +0 -78
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +0 -214
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +0 -102
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +0 -199
- /package/src/{calls → live-voice}/progress-phrases.ts +0 -0
|
@@ -29,11 +29,11 @@ export const HOLD_VERDICT_TOKEN = "[0]";
|
|
|
29
29
|
export const ESCALATE_VERDICT_TOKEN = "[1]";
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
32
|
+
* Emitted inline by the main/escalated voice leg to ask the live-voice
|
|
33
|
+
* client to minimize the voice room and reveal the screen behind it
|
|
34
|
+
* (e.g. after creating an app worth showing). Advisory: the daemon
|
|
35
|
+
* translates it into a `minimize_room` server frame after the turn's TTS
|
|
36
|
+
* drains; it is never spoken and never persisted.
|
|
37
37
|
*/
|
|
38
38
|
export const MINIMIZE_ROOM_MARKER = "[-1]";
|
|
39
39
|
|
|
@@ -192,9 +192,9 @@ export function stripInternalSpeechMarkers(text: string): string {
|
|
|
192
192
|
// ---------------------------------------------------------------------------
|
|
193
193
|
|
|
194
194
|
/**
|
|
195
|
-
* All known control marker prefixes. Used by
|
|
196
|
-
*
|
|
197
|
-
*
|
|
195
|
+
* All known control marker prefixes. Used by couldBeControlMarker to detect
|
|
196
|
+
* whether a buffer that starts with `[` might be the beginning of a control
|
|
197
|
+
* marker (and should therefore be held rather than flushed to TTS).
|
|
198
198
|
*/
|
|
199
199
|
const CONTROL_MARKER_STRINGS = [
|
|
200
200
|
"[ASK_GUARDIAN_APPROVAL:",
|
|
@@ -211,6 +211,19 @@ const CONTROL_MARKER_STRINGS = [
|
|
|
211
211
|
"[GUARDIAN_UNAVAILABLE]",
|
|
212
212
|
];
|
|
213
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Check whether `text` could be a partial or complete control marker.
|
|
216
|
+
*
|
|
217
|
+
* Returns true if any known marker string is a prefix of `text`
|
|
218
|
+
* (text starts with the marker) or `text` is a prefix of a marker
|
|
219
|
+
* (the marker starts with text — i.e. text is still being streamed).
|
|
220
|
+
*/
|
|
221
|
+
export function couldBeControlMarker(text: string): boolean {
|
|
222
|
+
return CONTROL_MARKER_STRINGS.some(
|
|
223
|
+
(marker) => marker.startsWith(text) || text.startsWith(marker),
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
214
227
|
// Colon-style markers whose bodies terminate at the first "]" — their strip
|
|
215
228
|
// regexes are non-greedy (`.+?\]`), so the first bracket IS the terminator.
|
|
216
229
|
// ASK_GUARDIAN_APPROVAL is deliberately absent: its balanced-JSON body may
|
|
@@ -254,48 +267,3 @@ export function isIncompleteControlMarkerTail(tail: string): boolean {
|
|
|
254
267
|
}
|
|
255
268
|
return false;
|
|
256
269
|
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Control-marker hygiene for one model leg's streamed text, shared by the
|
|
260
|
-
* phone call controller and the live-voice session. The returned flush is
|
|
261
|
-
* called with the leg's cumulative raw text so far and forwards, through
|
|
262
|
-
* `emit`, the stripped ({@link stripInternalSpeechMarkers}) prefix that has
|
|
263
|
-
* not been emitted yet and cannot contain a still-streaming control marker:
|
|
264
|
-
* the flush stops at the first "[" whose tail is an incomplete marker
|
|
265
|
-
* ({@link isIncompleteControlMarkerTail}) and holds from there until a later
|
|
266
|
-
* delta completes or disproves it. `force` (leg completion) emits the held
|
|
267
|
-
* tail so real text that merely resembles a marker prefix is not dropped.
|
|
268
|
-
*
|
|
269
|
-
* The scan runs forward from the emitted boundary, not from the last "[", so
|
|
270
|
-
* brackets INSIDE a streaming marker body (a JSON array or "]"-bearing string
|
|
271
|
-
* in ASK_GUARDIAN_APPROVAL) can neither mask the marker's start nor pass as
|
|
272
|
-
* its terminator. Markers are stripped, never acted on: a consumer that acts
|
|
273
|
-
* on a marker reads the leg's full raw text for it separately.
|
|
274
|
-
*/
|
|
275
|
-
export function createControlMarkerHoldback(
|
|
276
|
-
emit: (chunk: string) => void,
|
|
277
|
-
): (raw: string, opts?: { force?: boolean }) => void {
|
|
278
|
-
let emitted = 0;
|
|
279
|
-
return (raw, opts) => {
|
|
280
|
-
let safeEnd = raw.length;
|
|
281
|
-
if (opts?.force !== true) {
|
|
282
|
-
for (
|
|
283
|
-
let i = raw.indexOf("[", emitted);
|
|
284
|
-
i !== -1;
|
|
285
|
-
i = raw.indexOf("[", i + 1)
|
|
286
|
-
) {
|
|
287
|
-
if (isIncompleteControlMarkerTail(raw.slice(i))) {
|
|
288
|
-
safeEnd = i;
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
if (safeEnd > emitted) {
|
|
294
|
-
const chunk = stripInternalSpeechMarkers(raw.slice(emitted, safeEnd));
|
|
295
|
-
emitted = safeEnd;
|
|
296
|
-
if (chunk.length > 0) {
|
|
297
|
-
emit(chunk);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
}
|
|
@@ -188,34 +188,6 @@ function frontDoorRuleWithDigest(
|
|
|
188
188
|
});
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
/**
|
|
192
|
-
* The triage-and-escalate rule a turn's leg adds to its control prompt: the
|
|
193
|
-
* front-door leg decides and may hand off, the escalated leg continues the
|
|
194
|
-
* answer after a holding phrase was already spoken. Null when routing is off.
|
|
195
|
-
* One rule for both prompt shapes, the auto-built phone prompt and a
|
|
196
|
-
* caller-supplied one, so the front-door model is anchored to the same
|
|
197
|
-
* caller words on every transport.
|
|
198
|
-
*/
|
|
199
|
-
function routingLegRuleFor(
|
|
200
|
-
opts: Pick<
|
|
201
|
-
VoiceTurnOptions,
|
|
202
|
-
"routingLeg" | "unifiedVerdict" | "spokenEscalationBridge"
|
|
203
|
-
>,
|
|
204
|
-
callerUtterance: string,
|
|
205
|
-
): string | null {
|
|
206
|
-
switch (opts.routingLeg) {
|
|
207
|
-
case "front-door":
|
|
208
|
-
return frontDoorRuleWithDigest(
|
|
209
|
-
opts.unifiedVerdict === true,
|
|
210
|
-
callerUtterance,
|
|
211
|
-
);
|
|
212
|
-
case "escalated":
|
|
213
|
-
return escalatedContinuationRule(opts.spokenEscalationBridge);
|
|
214
|
-
default:
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
191
|
/**
|
|
220
192
|
* Exact message thrown when `opts.signal` aborts while the turn is waiting
|
|
221
193
|
* for the conversation to become available. The call controller's abort
|
|
@@ -401,9 +373,8 @@ export interface VoiceTurnOptions {
|
|
|
401
373
|
*
|
|
402
374
|
* Deliberately separate from {@link userMessageInterface}: that field feeds
|
|
403
375
|
* `resolveChannelCapabilities` and decides what the turn may do, so it is not
|
|
404
|
-
* free to carry attribution.
|
|
405
|
-
*
|
|
406
|
-
* clients that send no identity on the start frame.
|
|
376
|
+
* free to carry attribution. Absent for phone calls and for clients that send
|
|
377
|
+
* no identity on the start frame.
|
|
407
378
|
*/
|
|
408
379
|
voiceTelemetry?: {
|
|
409
380
|
sessionId: string;
|
|
@@ -476,7 +447,7 @@ export interface VoiceTurnOptions {
|
|
|
476
447
|
onComplete?: () => void;
|
|
477
448
|
/** Called when the agent loop encounters an error. */
|
|
478
449
|
onError?: (message: string) => void;
|
|
479
|
-
/** Event-name callbacks
|
|
450
|
+
/** Event-name callbacks used by non-phone voice clients. */
|
|
480
451
|
callbacks?: VoiceTurnCallbacks;
|
|
481
452
|
/**
|
|
482
453
|
* Called when this turn leaves a confirmation for the user to answer instead
|
|
@@ -511,9 +482,10 @@ export interface VoiceTurnOptions {
|
|
|
511
482
|
*/
|
|
512
483
|
overrideProfile?: string;
|
|
513
484
|
/**
|
|
514
|
-
* Which leg of a triaged turn this is, so the
|
|
515
|
-
*
|
|
516
|
-
*
|
|
485
|
+
* Which leg of a triaged turn this is, so the auto-built phone control prompt
|
|
486
|
+
* can add the front-door triage rule or the escalated continuation rule.
|
|
487
|
+
* Undefined = routing off; no routing rules are added. Ignored when a caller
|
|
488
|
+
* supplies its own `voiceControlPrompt`.
|
|
517
489
|
*/
|
|
518
490
|
routingLeg?: VoiceRoutingLeg;
|
|
519
491
|
/**
|
|
@@ -639,8 +611,9 @@ function buildVoiceCallControlPrompt(opts: {
|
|
|
639
611
|
task?: string | null;
|
|
640
612
|
isCallerGuardian?: boolean;
|
|
641
613
|
skipDisclosure?: boolean;
|
|
642
|
-
|
|
643
|
-
|
|
614
|
+
routingLeg?: VoiceRoutingLeg;
|
|
615
|
+
spokenEscalationBridge?: string;
|
|
616
|
+
unifiedVerdict?: boolean;
|
|
644
617
|
}): string {
|
|
645
618
|
const config = getConfig();
|
|
646
619
|
const disclosureEnabled =
|
|
@@ -692,18 +665,22 @@ function buildVoiceCallControlPrompt(opts: {
|
|
|
692
665
|
);
|
|
693
666
|
}
|
|
694
667
|
|
|
668
|
+
lines.push(
|
|
669
|
+
'6. When caller text includes [SPEAKER id="..." label="..."], treat each speaker as a distinct person and personalize responses using that speaker\'s prior context in this call.',
|
|
670
|
+
);
|
|
671
|
+
|
|
695
672
|
if (opts.isInbound) {
|
|
696
673
|
if (opts.isCallerGuardian) {
|
|
697
674
|
lines.push(
|
|
698
|
-
'
|
|
675
|
+
'7. If the latest user turn is "(call connected — deliver opening greeting)", this is your user calling you. Answer casually and briefly, like picking up a call from someone you know well. For example: "Hey!" or "What\'s up?" Do NOT introduce yourself, do NOT say you are calling on behalf of anyone, and do NOT ask how you can help in a formal way. Keep it short and natural.',
|
|
699
676
|
);
|
|
700
677
|
} else {
|
|
701
678
|
lines.push(
|
|
702
|
-
'
|
|
679
|
+
'7. If the latest user turn is "(call connected — deliver opening greeting)", this is an inbound call you are answering (not a call you initiated). Greet the caller warmly and ask how you can help. Introduce yourself once at the start using your assistant name if you know it (for example: "Hey there, this is Ava, Sam\'s assistant. How can I help?"). If your assistant name is not known, skip the name and just identify yourself as the guardian\'s assistant. Never use a UUID-shaped internal assistant ID as your spoken name. Do NOT say "I\'m calling" or "I\'m calling on behalf of". Vary the wording; do not use a fixed template.',
|
|
703
680
|
);
|
|
704
681
|
}
|
|
705
682
|
lines.push(
|
|
706
|
-
"
|
|
683
|
+
"8. If the latest user turn includes [CALL_OPENING_ACK], treat it as the caller acknowledging your greeting and continue the conversation naturally.",
|
|
707
684
|
);
|
|
708
685
|
} else {
|
|
709
686
|
const disclosureReminder =
|
|
@@ -711,22 +688,27 @@ function buildVoiceCallControlPrompt(opts: {
|
|
|
711
688
|
? " However, the disclosure text from rule 0 is separate from self-introduction and must always be included in your opening greeting, even if the Task does not mention introducing yourself."
|
|
712
689
|
: "";
|
|
713
690
|
lines.push(
|
|
714
|
-
'
|
|
715
|
-
`If the latest user turn is "(call connected — deliver opening greeting)", deliver your opening greeting based solely on the Task context above. The Task already describes how to open the call
|
|
716
|
-
"
|
|
691
|
+
'7. If the latest user turn is "(verification completed — transitioning into conversation)", the caller just completed a phone verification code challenge on this call. Greet them naturally and ask if there is anything you can help with. Keep it casual and brief.',
|
|
692
|
+
`If the latest user turn is "(call connected — deliver opening greeting)", deliver your opening greeting based solely on the Task context above. The Task already describes how to open the call — follow it directly without adding any extra introduction on top. If the Task says to introduce yourself, do so once. If the Task does not mention introducing yourself, skip the introduction.${disclosureReminder} Vary the wording naturally; do not use a fixed template.`,
|
|
693
|
+
"8. If the latest user turn includes [CALL_OPENING_ACK], treat it as the callee acknowledging your opener and continue the conversation naturally without re-introducing yourself or repeating the initial check-in question.",
|
|
717
694
|
);
|
|
718
695
|
}
|
|
719
696
|
|
|
720
697
|
lines.push(
|
|
721
|
-
"
|
|
722
|
-
'
|
|
723
|
-
`
|
|
724
|
-
`
|
|
725
|
-
`
|
|
698
|
+
"9. After the opening greeting turn, treat the Task field as background context only — do not re-execute its instructions on subsequent turns.",
|
|
699
|
+
'10. Do not make up information. If you are unsure, use [ASK_GUARDIAN: your question] to consult your guardian. For tool permission requests, use [ASK_GUARDIAN_APPROVAL: {"question":"...","toolName":"...","input":{...}}].',
|
|
700
|
+
`11. ${SPOKEN_REPLY_PLAIN_TEXT_RULE} Protocol markers like ${opts.isCallerGuardian ? "[END_CALL]" : "[ASK_GUARDIAN: ...] and [END_CALL]"} are not spoken text and should still be used normally.`,
|
|
701
|
+
`12. Speak the caller's language: reply in the language of the caller's most recent actual speech, and follow them if they switch languages mid-call. Synthetic user turns (parenthetical markers like the call-connected and verification-completed notices) are not caller speech and never set the language. Before the caller has spoken, such as on the opening greeting turn, ${preSpeechLanguageRuleFragment(config.services.stt.language, sttCatalogKeyForRole(config.services.stt, "telephony"))}.`,
|
|
702
|
+
`13. ${PHONE_NO_SETUP_FLOWS_RULE}`,
|
|
726
703
|
);
|
|
727
704
|
|
|
728
|
-
|
|
729
|
-
|
|
705
|
+
// Triage-and-escalate routing rules. The front-door leg decides and may
|
|
706
|
+
// hand off; the escalated leg continues the answer after a holding phrase
|
|
707
|
+
// was already spoken.
|
|
708
|
+
if (opts.routingLeg === "front-door") {
|
|
709
|
+
lines.push(`14. ${frontDoorRuleWithDigest(opts.unifiedVerdict === true)}`);
|
|
710
|
+
} else if (opts.routingLeg === "escalated") {
|
|
711
|
+
lines.push(`14. ${escalatedContinuationRule(opts.spokenEscalationBridge)}`);
|
|
730
712
|
}
|
|
731
713
|
|
|
732
714
|
lines.push("</voice_call_control>");
|
|
@@ -974,10 +956,6 @@ export async function startVoiceTurn(
|
|
|
974
956
|
// control markers (ASK_GUARDIAN, END_CALL, etc.) and recognize opener turns.
|
|
975
957
|
const isCallerGuardian = opts.trustContext?.trustClass === "guardian";
|
|
976
958
|
|
|
977
|
-
// The front-door rule anchors on the words the model sees as the user
|
|
978
|
-
// turn, which for a phone sentinel is its neutral persisted form rather
|
|
979
|
-
// than the marker itself.
|
|
980
|
-
const routingLegRule = routingLegRuleFor(opts, persistedContent);
|
|
981
959
|
let voiceCallControlPrompt: string | null;
|
|
982
960
|
if (opts.voiceControlPrompt === undefined) {
|
|
983
961
|
voiceCallControlPrompt = buildVoiceCallControlPrompt({
|
|
@@ -985,14 +963,23 @@ export async function startVoiceTurn(
|
|
|
985
963
|
task: opts.task,
|
|
986
964
|
isCallerGuardian,
|
|
987
965
|
skipDisclosure: opts.skipDisclosure,
|
|
988
|
-
|
|
966
|
+
routingLeg: opts.routingLeg,
|
|
967
|
+
spokenEscalationBridge: opts.spokenEscalationBridge,
|
|
968
|
+
unifiedVerdict: opts.unifiedVerdict,
|
|
989
969
|
});
|
|
990
970
|
} else {
|
|
991
|
-
// A caller-supplied prompt (live
|
|
992
|
-
//
|
|
993
|
-
//
|
|
994
|
-
//
|
|
971
|
+
// A caller-supplied prompt (e.g. live-voice) bypasses
|
|
972
|
+
// buildVoiceCallControlPrompt, which is where the triage-and-escalate rule
|
|
973
|
+
// is normally injected from `routingLeg`. Append it here too — without it
|
|
974
|
+
// the front-door leg would run on the fast profile but never learn the
|
|
975
|
+
// verdict protocol, so it could not hold or hand off to the escalated leg.
|
|
995
976
|
voiceCallControlPrompt = opts.voiceControlPrompt;
|
|
977
|
+
const routingLegRule =
|
|
978
|
+
opts.routingLeg === "front-door"
|
|
979
|
+
? frontDoorRuleWithDigest(opts.unifiedVerdict === true, opts.content)
|
|
980
|
+
: opts.routingLeg === "escalated"
|
|
981
|
+
? escalatedContinuationRule(opts.spokenEscalationBridge)
|
|
982
|
+
: null;
|
|
996
983
|
if (voiceCallControlPrompt != null && routingLegRule) {
|
|
997
984
|
voiceCallControlPrompt = `${voiceCallControlPrompt}\n\n${routingLegRule}`;
|
|
998
985
|
}
|
|
@@ -27,10 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { NON_LATIN_SENTENCE_ENDING_PUNCTUATION } from "../tts/speakable-segments.js";
|
|
30
|
-
import {
|
|
31
|
-
fixedPhraseLanguage,
|
|
32
|
-
localizedOrDefault,
|
|
33
|
-
} from "../util/language-subtag.js";
|
|
30
|
+
import { localizedOrDefault } from "../util/language-subtag.js";
|
|
34
31
|
import {
|
|
35
32
|
ESCALATE_VERDICT_TOKEN,
|
|
36
33
|
HOLD_VERDICT_TOKEN,
|
|
@@ -106,36 +103,6 @@ export function fallbackEscalationBridgeFor(language?: string): string {
|
|
|
106
103
|
*/
|
|
107
104
|
export const MIN_SPOKEN_BRIDGE_CHARS = 3;
|
|
108
105
|
|
|
109
|
-
/**
|
|
110
|
-
* The phrase spoken across an escalation hand-off, for both voice drivers:
|
|
111
|
-
* the front-door leg's own capped bridge when it is a real bridge, else the
|
|
112
|
-
* canned fallback in the caller's language. `usesFallback` marks the canned
|
|
113
|
-
* phrase as audio-only: the model never produced it, so no transcript row
|
|
114
|
-
* carries it (the bridge's hygiene pass deletes the leg's row), and the
|
|
115
|
-
* driver keeps it out of the turn's recorded text. `language` is the TTS
|
|
116
|
-
* hint the phrase must carry: "en" when the canned table has no entry for
|
|
117
|
-
* the caller's language (the phrase is English text then), undefined when
|
|
118
|
-
* the phrase rides the turn's own language.
|
|
119
|
-
*/
|
|
120
|
-
export function resolveSpokenEscalationBridge(
|
|
121
|
-
cappedBridge: string,
|
|
122
|
-
language?: string,
|
|
123
|
-
): { spokenBridge: string; usesFallback: boolean; language?: string } {
|
|
124
|
-
const usesFallback = cappedBridge.length < MIN_SPOKEN_BRIDGE_CHARS;
|
|
125
|
-
if (!usesFallback) {
|
|
126
|
-
return { spokenBridge: cappedBridge, usesFallback };
|
|
127
|
-
}
|
|
128
|
-
const fixedLanguage = fixedPhraseLanguage(
|
|
129
|
-
FALLBACK_ESCALATION_BRIDGE_BY_LANGUAGE,
|
|
130
|
-
language,
|
|
131
|
-
);
|
|
132
|
-
return {
|
|
133
|
-
spokenBridge: fallbackEscalationBridgeFor(language),
|
|
134
|
-
usesFallback,
|
|
135
|
-
...(fixedLanguage !== undefined ? { language: fixedLanguage } : {}),
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
106
|
/**
|
|
140
107
|
* Hard cap on the spoken escalation bridge. The bridge is supposed to be a
|
|
141
108
|
* single short sentence; the cap bounds the hand-off delay (and the audio)
|
|
@@ -192,20 +159,14 @@ export function isEscalationBridgeComplete(rawBridge: string): boolean {
|
|
|
192
159
|
* output leads with {@link ESCALATE_VERDICT_TOKEN} (a stray token later in
|
|
193
160
|
* an answer is not an escalation under the verdict-first protocol), else
|
|
194
161
|
* the capped bridge that followed the token. Used by transcript hygiene to
|
|
195
|
-
* reconstruct what the caller heard from a persisted row
|
|
196
|
-
* same verdict machine the live stream ran through.
|
|
162
|
+
* reconstruct what the caller heard from a persisted row.
|
|
197
163
|
*/
|
|
198
164
|
export function spokenBridgeText(frontDoorText: string): string {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
if (step.kind !== "escalate") {
|
|
165
|
+
const leading = frontDoorText.trimStart();
|
|
166
|
+
if (!leading.startsWith(ESCALATE_VERDICT_TOKEN)) {
|
|
202
167
|
return "";
|
|
203
168
|
}
|
|
204
|
-
|
|
205
|
-
return step.bridge;
|
|
206
|
-
}
|
|
207
|
-
const finished = machine.finish();
|
|
208
|
-
return finished.kind === "bridge" ? finished.bridge : "";
|
|
169
|
+
return capEscalationBridge(leading.slice(ESCALATE_VERDICT_TOKEN.length));
|
|
209
170
|
}
|
|
210
171
|
|
|
211
172
|
/**
|
|
@@ -263,187 +224,107 @@ export function classifyFrontDoorLeading(
|
|
|
263
224
|
}
|
|
264
225
|
|
|
265
226
|
/**
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
| { readonly kind: "pending" }
|
|
272
|
-
/** Hold verdict: the leg is discarded and listening continues. */
|
|
273
|
-
| { readonly kind: "hold" }
|
|
274
|
-
/**
|
|
275
|
-
* The leg's output is the answer. `text` is the raw text this delta
|
|
276
|
-
* released; on the transition it includes the leading text held while the
|
|
277
|
-
* verdict was pending.
|
|
278
|
-
*/
|
|
279
|
-
| { readonly kind: "answer"; readonly text: string }
|
|
280
|
-
/**
|
|
281
|
-
* Escalate verdict, decided by this delta. `bridge` is the capped holding
|
|
282
|
-
* phrase when this delta already completed it, else null while the phrase
|
|
283
|
-
* keeps streaming.
|
|
284
|
-
*/
|
|
285
|
-
| { readonly kind: "escalate"; readonly bridge: string | null }
|
|
286
|
-
/** Post-verdict text still buffering toward the capped bridge. */
|
|
287
|
-
| { readonly kind: "bridging" }
|
|
288
|
-
/** The capped bridge is complete: hand off to the escalated leg. */
|
|
289
|
-
| { readonly kind: "bridge"; readonly bridge: string }
|
|
290
|
-
/** The leg already held, handed off, or finished; nothing more happens. */
|
|
291
|
-
| { readonly kind: "done" };
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* The verdict-first state machine over a front-door leg's delta stream. One
|
|
295
|
-
* implementation drives every consumer of that stream: the live-voice
|
|
296
|
-
* session (which acts on the steps), the conversation-hub stream gate
|
|
297
|
-
* (which releases only the text the caller hears), and transcript hygiene
|
|
298
|
-
* (which replays a persisted row through it).
|
|
227
|
+
* Verdict-first gate over a front-door leg's delta stream: given the leg's
|
|
228
|
+
* raw deltas in order, it releases only the text the caller actually hears.
|
|
229
|
+
* A front-door leg's raw stream is a control plane, not assistant speech,
|
|
230
|
+
* until its leading tokens classify, so anything downstream of the model
|
|
231
|
+
* that shows text to a person reads the stream through this gate.
|
|
299
232
|
*
|
|
300
|
-
* `push`
|
|
301
|
-
* when the leg completes normally
|
|
302
|
-
*
|
|
303
|
-
* instead
|
|
233
|
+
* `push` returns the text released by that delta (empty while the gate is
|
|
234
|
+
* holding). `finish` is called when the leg completes normally and releases
|
|
235
|
+
* a bridge that stopped short of a sentence terminator. A leg that is
|
|
236
|
+
* cancelled instead spoke nothing past what `push` already released, so it
|
|
237
|
+
* simply never calls `finish`.
|
|
304
238
|
*/
|
|
305
|
-
export interface
|
|
306
|
-
push(deltaText: string):
|
|
307
|
-
finish():
|
|
239
|
+
export interface FrontDoorStreamGate {
|
|
240
|
+
push(deltaText: string): string;
|
|
241
|
+
finish(): string;
|
|
308
242
|
}
|
|
309
243
|
|
|
310
|
-
const PENDING_STEP: FrontDoorStep = { kind: "pending" };
|
|
311
|
-
const HOLD_STEP: FrontDoorStep = { kind: "hold" };
|
|
312
|
-
const BRIDGING_STEP: FrontDoorStep = { kind: "bridging" };
|
|
313
|
-
const DONE_STEP: FrontDoorStep = { kind: "done" };
|
|
314
|
-
|
|
315
244
|
/**
|
|
316
|
-
* Build a {@link
|
|
245
|
+
* Build a {@link FrontDoorStreamGate}. `holdEnabled` mirrors
|
|
317
246
|
* {@link classifyFrontDoorLeading}: true only for speculative (unified
|
|
318
247
|
* front-door) legs, whose decision rule is the only one that teaches the
|
|
319
248
|
* hold token.
|
|
320
249
|
*
|
|
321
|
-
* The
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
250
|
+
* The three verdicts release differently, matching what the caller hears:
|
|
251
|
+
*
|
|
252
|
+
* - `hold`: the leg is discarded and its row deleted, so nothing is ever
|
|
253
|
+
* released.
|
|
254
|
+
* - `escalate`: the only spoken text is the capped holding phrase, released
|
|
255
|
+
* in one piece once the bridge is complete (exactly what
|
|
256
|
+
* {@link capEscalationBridge} yields, so the released text, the audio, and
|
|
257
|
+
* the persisted row agree). The verdict token and anything streamed past
|
|
258
|
+
* the cap are dropped. A bridge shorter than
|
|
259
|
+
* {@link MIN_SPOKEN_BRIDGE_CHARS} releases nothing at all: the session
|
|
260
|
+
* substitutes an audio-only canned fallback for it, so there is no
|
|
261
|
+
* displayed text for the gate to agree with.
|
|
262
|
+
* - `answer`: the leg's output IS the reply, so every delta passes through,
|
|
263
|
+
* including the leading text held back while the verdict was pending.
|
|
325
264
|
*/
|
|
326
|
-
export function
|
|
265
|
+
export function createFrontDoorStreamGate(
|
|
327
266
|
holdEnabled: boolean,
|
|
328
|
-
):
|
|
267
|
+
): FrontDoorStreamGate {
|
|
329
268
|
let raw = "";
|
|
330
269
|
let stage: "deciding" | "answer" | "bridging" | "done" = "deciding";
|
|
331
270
|
let bridgeRaw = "";
|
|
332
271
|
let releasedChars = 0;
|
|
333
272
|
|
|
334
|
-
const
|
|
273
|
+
const releaseBridge = (): string => {
|
|
335
274
|
stage = "done";
|
|
336
|
-
|
|
275
|
+
const capped = capEscalationBridge(bridgeRaw);
|
|
276
|
+
// Below the spoken threshold the session throws the model's bridge away
|
|
277
|
+
// and plays a canned fallback that is audio-only, deleting the row rather
|
|
278
|
+
// than persisting a phrase the model never really produced (see
|
|
279
|
+
// `usesFallbackBridge` in `live-voice-session.ts`). Releasing the capped
|
|
280
|
+
// text here would put words on a subscriber's screen that the caller never
|
|
281
|
+
// heard, which is the same spoken/displayed divergence this gate exists to
|
|
282
|
+
// prevent.
|
|
283
|
+
return capped.length < MIN_SPOKEN_BRIDGE_CHARS ? "" : capped;
|
|
337
284
|
};
|
|
338
285
|
|
|
339
286
|
return {
|
|
340
|
-
push(deltaText: string):
|
|
287
|
+
push(deltaText: string): string {
|
|
341
288
|
raw += deltaText;
|
|
342
289
|
if (stage === "done") {
|
|
343
|
-
return
|
|
290
|
+
return "";
|
|
344
291
|
}
|
|
345
292
|
if (stage === "bridging") {
|
|
346
293
|
bridgeRaw += deltaText;
|
|
347
|
-
return isEscalationBridgeComplete(bridgeRaw)
|
|
348
|
-
? completeBridge()
|
|
349
|
-
: BRIDGING_STEP;
|
|
294
|
+
return isEscalationBridgeComplete(bridgeRaw) ? releaseBridge() : "";
|
|
350
295
|
}
|
|
351
296
|
if (stage === "deciding") {
|
|
352
297
|
const verdict = classifyFrontDoorLeading(raw.trimStart(), holdEnabled);
|
|
353
298
|
if (verdict === "pending") {
|
|
354
|
-
return
|
|
299
|
+
return "";
|
|
355
300
|
}
|
|
356
301
|
if (verdict === "hold") {
|
|
357
302
|
stage = "done";
|
|
358
|
-
return
|
|
303
|
+
return "";
|
|
359
304
|
}
|
|
360
305
|
if (verdict === "escalate") {
|
|
361
306
|
stage = "bridging";
|
|
362
307
|
bridgeRaw = raw.trimStart().slice(ESCALATE_VERDICT_TOKEN.length);
|
|
363
|
-
return
|
|
364
|
-
kind: "escalate",
|
|
365
|
-
bridge: isEscalationBridgeComplete(bridgeRaw)
|
|
366
|
-
? completeBridge().bridge
|
|
367
|
-
: null,
|
|
368
|
-
};
|
|
308
|
+
return isEscalationBridgeComplete(bridgeRaw) ? releaseBridge() : "";
|
|
369
309
|
}
|
|
370
310
|
stage = "answer";
|
|
371
311
|
}
|
|
372
312
|
// Answer stage: release everything not yet released, which on the
|
|
373
313
|
// transition includes the leading text the pending verdict held.
|
|
374
|
-
const
|
|
314
|
+
const chunk = raw.slice(releasedChars);
|
|
375
315
|
releasedChars = raw.length;
|
|
376
|
-
return
|
|
316
|
+
return chunk;
|
|
377
317
|
},
|
|
378
|
-
finish():
|
|
318
|
+
finish(): string {
|
|
379
319
|
if (stage === "bridging") {
|
|
380
|
-
return
|
|
320
|
+
return releaseBridge();
|
|
381
321
|
}
|
|
382
322
|
stage = "done";
|
|
383
|
-
return
|
|
323
|
+
return "";
|
|
384
324
|
},
|
|
385
325
|
};
|
|
386
326
|
}
|
|
387
327
|
|
|
388
|
-
/**
|
|
389
|
-
* Verdict-first gate over a front-door leg's delta stream: given the leg's
|
|
390
|
-
* raw deltas in order, it releases only the text the caller actually hears.
|
|
391
|
-
* A front-door leg's raw stream is a control plane, not assistant speech,
|
|
392
|
-
* until its leading tokens classify, so anything downstream of the model
|
|
393
|
-
* that shows text to a person reads the stream through this gate.
|
|
394
|
-
*
|
|
395
|
-
* `push` returns the text released by that delta (empty while the gate is
|
|
396
|
-
* holding). `finish` is called when the leg completes normally and releases
|
|
397
|
-
* a bridge that stopped short of a sentence terminator. A leg that is
|
|
398
|
-
* cancelled instead spoke nothing past what `push` already released, so it
|
|
399
|
-
* simply never calls `finish`.
|
|
400
|
-
*/
|
|
401
|
-
export interface FrontDoorStreamGate {
|
|
402
|
-
push(deltaText: string): string;
|
|
403
|
-
finish(): string;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Build a {@link FrontDoorStreamGate} over a {@link FrontDoorVerdictMachine}.
|
|
408
|
-
*
|
|
409
|
-
* The three verdicts release differently, matching what the caller hears:
|
|
410
|
-
*
|
|
411
|
-
* - `hold`: the leg is discarded and its row deleted, so nothing is ever
|
|
412
|
-
* released.
|
|
413
|
-
* - `escalate`: the only spoken text is the capped holding phrase, released
|
|
414
|
-
* in one piece once the bridge is complete. A bridge shorter than
|
|
415
|
-
* {@link MIN_SPOKEN_BRIDGE_CHARS} releases nothing at all: the session
|
|
416
|
-
* substitutes an audio-only canned fallback for it (see
|
|
417
|
-
* `usesFallbackBridge` in `live-voice-session.ts`) and deletes the row
|
|
418
|
-
* rather than persisting a phrase the model never really produced, so
|
|
419
|
-
* there is no displayed text for the gate to agree with.
|
|
420
|
-
* - `answer`: the leg's output IS the reply, so every delta passes through,
|
|
421
|
-
* including the leading text held back while the verdict was pending.
|
|
422
|
-
*/
|
|
423
|
-
export function createFrontDoorStreamGate(
|
|
424
|
-
holdEnabled: boolean,
|
|
425
|
-
): FrontDoorStreamGate {
|
|
426
|
-
const machine = createFrontDoorVerdictMachine(holdEnabled);
|
|
427
|
-
const releasable = (bridge: string): string =>
|
|
428
|
-
bridge.length < MIN_SPOKEN_BRIDGE_CHARS ? "" : bridge;
|
|
429
|
-
const release = (step: FrontDoorStep): string => {
|
|
430
|
-
switch (step.kind) {
|
|
431
|
-
case "answer":
|
|
432
|
-
return step.text;
|
|
433
|
-
case "escalate":
|
|
434
|
-
return step.bridge === null ? "" : releasable(step.bridge);
|
|
435
|
-
case "bridge":
|
|
436
|
-
return releasable(step.bridge);
|
|
437
|
-
default:
|
|
438
|
-
return "";
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
return {
|
|
442
|
-
push: (deltaText) => release(machine.push(deltaText)),
|
|
443
|
-
finish: () => release(machine.finish()),
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
328
|
/**
|
|
448
329
|
* The escalated leg runs as its own voice turn. Rather than re-persist the
|
|
449
330
|
* caller's utterance (it is already in history from the front-door leg), the
|