@vellumai/assistant 0.12.1 → 0.12.2-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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
- package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
- package/openapi.yaml +360 -51
- package/package.json +3 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +19 -7
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- 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 +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +250 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/cu-unified-flow.test.ts +14 -4
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/heartbeat-service.test.ts +56 -40
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-cu-proxy.test.ts +596 -0
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/oauth-commands-routes.test.ts +168 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
- package/src/__tests__/service-account-token.test.ts +247 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/ui-channel-variants.test.ts +1 -56
- package/src/__tests__/unicode.test.ts +13 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
- package/src/acp/__tests__/acp-credentials.test.ts +13 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/acp-claude-oauth.ts +328 -14
- package/src/acp/acp-credentials.ts +19 -0
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +96 -10
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/claude-token-refresh.ts +150 -0
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/prepare-agent-env.ts +21 -6
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/agent/loop.ts +1 -0
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/events/notification-intent.ts +2 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/avatar/identity-avatar.ts +7 -2
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +155 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +92 -72
- package/src/calls/voice-triage-escalate.ts +178 -59
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- 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/oauth/status.ts +5 -0
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +33 -13
- package/src/config/loader.ts +2 -0
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/services.ts +18 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/credential-health/credential-health-service.ts +11 -17
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-runtime-assembly.ts +20 -7
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +21 -10
- package/src/daemon/conversation-tool-setup.ts +45 -59
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-cu-proxy.ts +224 -19
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/reaction-history-render.ts +2 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +122 -0
- package/src/desktop/desktop-chrome-policy.test.ts +79 -0
- package/src/desktop/desktop-chrome-policy.ts +47 -0
- package/src/desktop/desktop-chrome-session.ts +37 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +308 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +730 -0
- package/src/desktop/desktop-session-manager.ts +850 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-drag.ts +177 -0
- package/src/desktop/desktop-window-manager-config.test.ts +230 -0
- package/src/desktop/desktop-window-manager-config.ts +238 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/heartbeat/heartbeat-service.ts +21 -3
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/protocol.test.ts +39 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/__tests__/session-controls.test.ts +79 -0
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +297 -699
- package/src/live-voice/protocol.ts +60 -2
- package/src/live-voice/session-controls.ts +111 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
- package/src/oauth/byo-connection.test.ts +105 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +100 -0
- package/src/oauth/connection-resolver.ts +120 -3
- package/src/oauth/provider-serializer.ts +9 -0
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +314 -27
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-queries.ts +2 -2
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- 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-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +4 -2
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/host-utils.ts +10 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +28 -2
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/__tests__/retry-callsite.test.ts +41 -0
- package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/endpoint-probe.ts +10 -1
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +212 -79
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/providers/opencode/client.test.ts +73 -4
- package/src/providers/opencode/client.ts +60 -3
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +30 -19
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +3 -1
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/host-observe.test.ts +40 -1
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/host-observe.ts +11 -1
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +90 -2
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/conversation-routes.ts +16 -3
- package/src/runtime/routes/credential-routes.ts +20 -14
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +61 -4
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/index.ts +4 -2
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +6 -20
- package/src/runtime/routes/oauth-commands-routes.ts +50 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/security/untrusted-content.ts +3 -1
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +121 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/filesystem/search.ts +2 -1
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/network/web-search.ts +6 -1
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/ui-surface/channel-variants.ts +10 -59
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/types/host-capabilities.ts +5 -3
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/util/unicode.ts +15 -1
- package/src/watch/watch-retro.ts +6 -8
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +6 -55
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -70,8 +70,8 @@ import {
|
|
|
70
70
|
CALL_VERIFICATION_COMPLETE_MARKER,
|
|
71
71
|
ESCALATE_VERDICT_TOKEN,
|
|
72
72
|
HOLD_VERDICT_TOKEN,
|
|
73
|
-
MINIMIZE_ROOM_MARKER,
|
|
74
73
|
stripInternalSpeechMarkers,
|
|
74
|
+
terminalControlMarkerLength,
|
|
75
75
|
} from "./voice-control-protocol.js";
|
|
76
76
|
import {
|
|
77
77
|
createFrontDoorStreamGate,
|
|
@@ -188,6 +188,34 @@ 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
|
+
|
|
191
219
|
/**
|
|
192
220
|
* Exact message thrown when `opts.signal` aborts while the turn is waiting
|
|
193
221
|
* for the conversation to become available. The call controller's abort
|
|
@@ -373,8 +401,9 @@ export interface VoiceTurnOptions {
|
|
|
373
401
|
*
|
|
374
402
|
* Deliberately separate from {@link userMessageInterface}: that field feeds
|
|
375
403
|
* `resolveChannelCapabilities` and decides what the turn may do, so it is not
|
|
376
|
-
* free to carry attribution.
|
|
377
|
-
*
|
|
404
|
+
* free to carry attribution. A phone call passes its call session id and a
|
|
405
|
+
* `phone_*` entry naming the call's direction, with no client. Absent for
|
|
406
|
+
* clients that send no identity on the start frame.
|
|
378
407
|
*/
|
|
379
408
|
voiceTelemetry?: {
|
|
380
409
|
sessionId: string;
|
|
@@ -447,7 +476,7 @@ export interface VoiceTurnOptions {
|
|
|
447
476
|
onComplete?: () => void;
|
|
448
477
|
/** Called when the agent loop encounters an error. */
|
|
449
478
|
onError?: (message: string) => void;
|
|
450
|
-
/** Event-name callbacks
|
|
479
|
+
/** Event-name callbacks: tool activity, persisted row ids, raw stream. */
|
|
451
480
|
callbacks?: VoiceTurnCallbacks;
|
|
452
481
|
/**
|
|
453
482
|
* Called when this turn leaves a confirmation for the user to answer instead
|
|
@@ -482,10 +511,9 @@ export interface VoiceTurnOptions {
|
|
|
482
511
|
*/
|
|
483
512
|
overrideProfile?: string;
|
|
484
513
|
/**
|
|
485
|
-
* Which leg of a triaged turn this is, so the auto-built
|
|
486
|
-
*
|
|
487
|
-
* Undefined = routing off; no routing rules are added.
|
|
488
|
-
* supplies its own `voiceControlPrompt`.
|
|
514
|
+
* Which leg of a triaged turn this is, so the control prompt (auto-built or
|
|
515
|
+
* caller-supplied) carries the front-door triage rule or the escalated
|
|
516
|
+
* continuation rule. Undefined = routing off; no routing rules are added.
|
|
489
517
|
*/
|
|
490
518
|
routingLeg?: VoiceRoutingLeg;
|
|
491
519
|
/**
|
|
@@ -611,9 +639,8 @@ function buildVoiceCallControlPrompt(opts: {
|
|
|
611
639
|
task?: string | null;
|
|
612
640
|
isCallerGuardian?: boolean;
|
|
613
641
|
skipDisclosure?: boolean;
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
unifiedVerdict?: boolean;
|
|
642
|
+
/** The turn's routing-leg rule (see {@link routingLegRuleFor}), if any. */
|
|
643
|
+
routingLegRule?: string | null;
|
|
617
644
|
}): string {
|
|
618
645
|
const config = getConfig();
|
|
619
646
|
const disclosureEnabled =
|
|
@@ -665,22 +692,18 @@ function buildVoiceCallControlPrompt(opts: {
|
|
|
665
692
|
);
|
|
666
693
|
}
|
|
667
694
|
|
|
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
|
-
|
|
672
695
|
if (opts.isInbound) {
|
|
673
696
|
if (opts.isCallerGuardian) {
|
|
674
697
|
lines.push(
|
|
675
|
-
'
|
|
698
|
+
'6. 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.',
|
|
676
699
|
);
|
|
677
700
|
} else {
|
|
678
701
|
lines.push(
|
|
679
|
-
'
|
|
702
|
+
'6. 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.',
|
|
680
703
|
);
|
|
681
704
|
}
|
|
682
705
|
lines.push(
|
|
683
|
-
"
|
|
706
|
+
"7. If the latest user turn includes [CALL_OPENING_ACK], treat it as the caller acknowledging your greeting and continue the conversation naturally.",
|
|
684
707
|
);
|
|
685
708
|
} else {
|
|
686
709
|
const disclosureReminder =
|
|
@@ -688,27 +711,22 @@ function buildVoiceCallControlPrompt(opts: {
|
|
|
688
711
|
? " 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."
|
|
689
712
|
: "";
|
|
690
713
|
lines.push(
|
|
691
|
-
'
|
|
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
|
|
693
|
-
"
|
|
714
|
+
'6. 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.',
|
|
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, so 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.`,
|
|
716
|
+
"7. 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.",
|
|
694
717
|
);
|
|
695
718
|
}
|
|
696
719
|
|
|
697
720
|
lines.push(
|
|
698
|
-
"
|
|
699
|
-
'
|
|
700
|
-
`
|
|
701
|
-
`
|
|
702
|
-
`
|
|
721
|
+
"8. After the opening greeting turn, treat the Task field as background context only. Do not re-execute its instructions on subsequent turns.",
|
|
722
|
+
'9. 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":{...}}].',
|
|
723
|
+
`10. ${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.`,
|
|
724
|
+
`11. 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"))}.`,
|
|
725
|
+
`12. ${PHONE_NO_SETUP_FLOWS_RULE}`,
|
|
703
726
|
);
|
|
704
727
|
|
|
705
|
-
|
|
706
|
-
|
|
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)}`);
|
|
728
|
+
if (opts.routingLegRule) {
|
|
729
|
+
lines.push(`13. ${opts.routingLegRule}`);
|
|
712
730
|
}
|
|
713
731
|
|
|
714
732
|
lines.push("</voice_call_control>");
|
|
@@ -749,17 +767,22 @@ function stripMarkersFromBlocks(blocks: ContentBlock[]): ContentBlock[] {
|
|
|
749
767
|
}
|
|
750
768
|
|
|
751
769
|
/**
|
|
752
|
-
* Remove
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
770
|
+
* Remove a terminal control marker (the minimize marker or a session control,
|
|
771
|
+
* `markerLength` characters long) from the end of a row's text, walking text
|
|
772
|
+
* blocks from the last one backward so a marker split across block boundaries
|
|
773
|
+
* (e.g. `"Done [-"` + `"1]"`) is removed whole — the per-block strip in
|
|
774
|
+
* {@link stripMarkersFromBlocks} only sees fragments and would leave both
|
|
775
|
+
* halves in place. Callers must have established, with
|
|
776
|
+
* {@link terminalControlMarkerLength}, that the row's joined text ends with
|
|
777
|
+
* the marker after trimming trailing whitespace.
|
|
758
778
|
*/
|
|
759
|
-
function
|
|
779
|
+
function stripTerminalControlMarker(
|
|
780
|
+
blocks: ContentBlock[],
|
|
781
|
+
markerLength: number,
|
|
782
|
+
): ContentBlock[] {
|
|
760
783
|
const result = blocks.map((block) => ({ ...block }));
|
|
761
784
|
const joined = joinedTextOfBlocks(result);
|
|
762
|
-
const cutAt = joined.trimEnd().length -
|
|
785
|
+
const cutAt = joined.trimEnd().length - markerLength;
|
|
763
786
|
let blockEnd = joined.length;
|
|
764
787
|
for (let i = result.length - 1; i >= 0 && blockEnd > cutAt; i--) {
|
|
765
788
|
const block = result[i]!;
|
|
@@ -956,6 +979,10 @@ export async function startVoiceTurn(
|
|
|
956
979
|
// control markers (ASK_GUARDIAN, END_CALL, etc.) and recognize opener turns.
|
|
957
980
|
const isCallerGuardian = opts.trustContext?.trustClass === "guardian";
|
|
958
981
|
|
|
982
|
+
// The front-door rule anchors on the words the model sees as the user
|
|
983
|
+
// turn, which for a phone sentinel is its neutral persisted form rather
|
|
984
|
+
// than the marker itself.
|
|
985
|
+
const routingLegRule = routingLegRuleFor(opts, persistedContent);
|
|
959
986
|
let voiceCallControlPrompt: string | null;
|
|
960
987
|
if (opts.voiceControlPrompt === undefined) {
|
|
961
988
|
voiceCallControlPrompt = buildVoiceCallControlPrompt({
|
|
@@ -963,23 +990,14 @@ export async function startVoiceTurn(
|
|
|
963
990
|
task: opts.task,
|
|
964
991
|
isCallerGuardian,
|
|
965
992
|
skipDisclosure: opts.skipDisclosure,
|
|
966
|
-
|
|
967
|
-
spokenEscalationBridge: opts.spokenEscalationBridge,
|
|
968
|
-
unifiedVerdict: opts.unifiedVerdict,
|
|
993
|
+
routingLegRule,
|
|
969
994
|
});
|
|
970
995
|
} else {
|
|
971
|
-
// A caller-supplied prompt (
|
|
972
|
-
//
|
|
973
|
-
//
|
|
974
|
-
//
|
|
975
|
-
// verdict protocol, so it could not hold or hand off to the escalated leg.
|
|
996
|
+
// A caller-supplied prompt (live voice) replaces the phone prompt, so the
|
|
997
|
+
// routing-leg rule is appended to it here. Without it the front-door leg
|
|
998
|
+
// would run on the fast profile but never learn the verdict protocol, so
|
|
999
|
+
// it could not hold or hand off to the escalated leg.
|
|
976
1000
|
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;
|
|
983
1001
|
if (voiceCallControlPrompt != null && routingLegRule) {
|
|
984
1002
|
voiceCallControlPrompt = `${voiceCallControlPrompt}\n\n${routingLegRule}`;
|
|
985
1003
|
}
|
|
@@ -1797,14 +1815,14 @@ export async function startVoiceTurn(
|
|
|
1797
1815
|
* never the verdict token or the text streamed past the cap (issue
|
|
1798
1816
|
* #37850). A row with no spoken bridge (canned-fallback case — that
|
|
1799
1817
|
* bridge is audio-only) is deleted.
|
|
1800
|
-
* - Any leg whose row ENDS with the `[-1]` minimize marker
|
|
1801
|
-
*
|
|
1818
|
+
* - Any leg whose row ENDS with the `[-1]` minimize marker or a session
|
|
1819
|
+
* control marker (`[END_CALL]`, `[MUTE]`, `[MUTE:<seconds>]`), all
|
|
1820
|
+
* swallowed before TTS, has its text blocks rewritten through
|
|
1802
1821
|
* `stripInternalSpeechMarkers` so the marker never renders in the chat
|
|
1803
|
-
* transcript. This covers front-door answers too:
|
|
1804
|
-
*
|
|
1805
|
-
* history
|
|
1806
|
-
*
|
|
1807
|
-
* persist byte-identical.
|
|
1822
|
+
* transcript. This covers front-door answers too: a front-door answer
|
|
1823
|
+
* may end with a session control, and it can parrot `[-1]` from visible
|
|
1824
|
+
* conversation history. Deliberately scoped to terminal markers: rows
|
|
1825
|
+
* without one persist byte-identical.
|
|
1808
1826
|
*
|
|
1809
1827
|
* After a rewrite, in-memory history is reloaded from the clean DB before
|
|
1810
1828
|
* the escalated leg — blocked on this turn's teardown — snapshots it, so
|
|
@@ -1838,6 +1856,9 @@ export async function startVoiceTurn(
|
|
|
1838
1856
|
action = "delete_discarded";
|
|
1839
1857
|
} else {
|
|
1840
1858
|
const row = getMessageById(reservedAssistantRowId, opts.conversationId);
|
|
1859
|
+
const terminalMarkerLength = row
|
|
1860
|
+
? terminalControlMarkerLength(joinedTextOfBlocks(row.content))
|
|
1861
|
+
: 0;
|
|
1841
1862
|
const cut =
|
|
1842
1863
|
row && opts.routingLeg === "front-door"
|
|
1843
1864
|
? cutFrontDoorContentAtVerdict(row.content)
|
|
@@ -1856,19 +1877,18 @@ export async function startVoiceTurn(
|
|
|
1856
1877
|
action = "delete_empty";
|
|
1857
1878
|
}
|
|
1858
1879
|
} else if (
|
|
1859
|
-
// Terminal position only — mirrors
|
|
1860
|
-
//
|
|
1861
|
-
//
|
|
1862
|
-
//
|
|
1863
|
-
|
|
1864
|
-
joinedTextOfBlocks(row.content)
|
|
1865
|
-
.trimEnd()
|
|
1866
|
-
.endsWith(MINIMIZE_ROOM_MARKER)
|
|
1880
|
+
// Terminal position only — mirrors parseTerminalSessionControl: a
|
|
1881
|
+
// reply whose CONTENT contains a marker mid-text never acted on it,
|
|
1882
|
+
// so its transcript keeps that content untouched too. Front-door
|
|
1883
|
+
// answer rows (no verdict token to cut) take this branch as well.
|
|
1884
|
+
terminalMarkerLength > 0
|
|
1867
1885
|
) {
|
|
1868
1886
|
// Terminal marker first (boundary-aware — it may span text blocks),
|
|
1869
1887
|
// then the per-block strip for any interior complete markers.
|
|
1870
1888
|
const cleaned = trimOuterTextEdges(
|
|
1871
|
-
stripMarkersFromBlocks(
|
|
1889
|
+
stripMarkersFromBlocks(
|
|
1890
|
+
stripTerminalControlMarker(row.content, terminalMarkerLength),
|
|
1891
|
+
),
|
|
1872
1892
|
);
|
|
1873
1893
|
// A marker-only reply (the model said nothing beyond "[-1]") strips
|
|
1874
1894
|
// to nothing at all; keeping the row would render a blank assistant
|
|
@@ -1882,7 +1902,7 @@ export async function startVoiceTurn(
|
|
|
1882
1902
|
reservedAssistantRowId,
|
|
1883
1903
|
JSON.stringify(cleaned),
|
|
1884
1904
|
);
|
|
1885
|
-
action = "
|
|
1905
|
+
action = "strip_control_marker";
|
|
1886
1906
|
}
|
|
1887
1907
|
}
|
|
1888
1908
|
}
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { NON_LATIN_SENTENCE_ENDING_PUNCTUATION } from "../tts/speakable-segments.js";
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
fixedPhraseLanguage,
|
|
32
|
+
localizedOrDefault,
|
|
33
|
+
} from "../util/language-subtag.js";
|
|
31
34
|
import {
|
|
32
35
|
ESCALATE_VERDICT_TOKEN,
|
|
33
36
|
HOLD_VERDICT_TOKEN,
|
|
@@ -103,6 +106,36 @@ export function fallbackEscalationBridgeFor(language?: string): string {
|
|
|
103
106
|
*/
|
|
104
107
|
export const MIN_SPOKEN_BRIDGE_CHARS = 3;
|
|
105
108
|
|
|
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
|
+
|
|
106
139
|
/**
|
|
107
140
|
* Hard cap on the spoken escalation bridge. The bridge is supposed to be a
|
|
108
141
|
* single short sentence; the cap bounds the hand-off delay (and the audio)
|
|
@@ -159,14 +192,20 @@ export function isEscalationBridgeComplete(rawBridge: string): boolean {
|
|
|
159
192
|
* output leads with {@link ESCALATE_VERDICT_TOKEN} (a stray token later in
|
|
160
193
|
* an answer is not an escalation under the verdict-first protocol), else
|
|
161
194
|
* the capped bridge that followed the token. Used by transcript hygiene to
|
|
162
|
-
* reconstruct what the caller heard from a persisted row
|
|
195
|
+
* reconstruct what the caller heard from a persisted row, by running the
|
|
196
|
+
* same verdict machine the live stream ran through.
|
|
163
197
|
*/
|
|
164
198
|
export function spokenBridgeText(frontDoorText: string): string {
|
|
165
|
-
const
|
|
166
|
-
|
|
199
|
+
const machine = createFrontDoorVerdictMachine(false);
|
|
200
|
+
const step = machine.push(frontDoorText);
|
|
201
|
+
if (step.kind !== "escalate") {
|
|
167
202
|
return "";
|
|
168
203
|
}
|
|
169
|
-
|
|
204
|
+
if (step.bridge !== null) {
|
|
205
|
+
return step.bridge;
|
|
206
|
+
}
|
|
207
|
+
const finished = machine.finish();
|
|
208
|
+
return finished.kind === "bridge" ? finished.bridge : "";
|
|
170
209
|
}
|
|
171
210
|
|
|
172
211
|
/**
|
|
@@ -224,107 +263,187 @@ export function classifyFrontDoorLeading(
|
|
|
224
263
|
}
|
|
225
264
|
|
|
226
265
|
/**
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
266
|
+
* One step of a front-door leg's stream under the verdict-first protocol,
|
|
267
|
+
* as returned by {@link FrontDoorVerdictMachine.push}.
|
|
268
|
+
*/
|
|
269
|
+
export type FrontDoorStep =
|
|
270
|
+
/** The leading tokens could still become a verdict token: keep buffering. */
|
|
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).
|
|
232
299
|
*
|
|
233
|
-
* `push`
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* simply never calls `finish`.
|
|
300
|
+
* `push` feeds one delta and reports what it decided. `finish` is called
|
|
301
|
+
* when the leg completes normally: a bridge that stopped short of a
|
|
302
|
+
* sentence terminator hands off with what arrived. A leg that is cancelled
|
|
303
|
+
* instead never calls `finish`.
|
|
238
304
|
*/
|
|
239
|
-
export interface
|
|
240
|
-
push(deltaText: string):
|
|
241
|
-
finish():
|
|
305
|
+
export interface FrontDoorVerdictMachine {
|
|
306
|
+
push(deltaText: string): FrontDoorStep;
|
|
307
|
+
finish(): FrontDoorStep;
|
|
242
308
|
}
|
|
243
309
|
|
|
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
|
+
|
|
244
315
|
/**
|
|
245
|
-
* Build a {@link
|
|
316
|
+
* Build a {@link FrontDoorVerdictMachine}. `holdEnabled` mirrors
|
|
246
317
|
* {@link classifyFrontDoorLeading}: true only for speculative (unified
|
|
247
318
|
* front-door) legs, whose decision rule is the only one that teaches the
|
|
248
319
|
* hold token.
|
|
249
320
|
*
|
|
250
|
-
* The
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
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.
|
|
321
|
+
* The bridge handed off is exactly what {@link capEscalationBridge} yields,
|
|
322
|
+
* so the audio, the released text, the persisted row, and the phrase quoted
|
|
323
|
+
* to the escalated leg all agree. The verdict token and anything streamed
|
|
324
|
+
* past the cap are never released.
|
|
264
325
|
*/
|
|
265
|
-
export function
|
|
326
|
+
export function createFrontDoorVerdictMachine(
|
|
266
327
|
holdEnabled: boolean,
|
|
267
|
-
):
|
|
328
|
+
): FrontDoorVerdictMachine {
|
|
268
329
|
let raw = "";
|
|
269
330
|
let stage: "deciding" | "answer" | "bridging" | "done" = "deciding";
|
|
270
331
|
let bridgeRaw = "";
|
|
271
332
|
let releasedChars = 0;
|
|
272
333
|
|
|
273
|
-
const
|
|
334
|
+
const completeBridge = (): Extract<FrontDoorStep, { kind: "bridge" }> => {
|
|
274
335
|
stage = "done";
|
|
275
|
-
|
|
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;
|
|
336
|
+
return { kind: "bridge", bridge: capEscalationBridge(bridgeRaw) };
|
|
284
337
|
};
|
|
285
338
|
|
|
286
339
|
return {
|
|
287
|
-
push(deltaText: string):
|
|
340
|
+
push(deltaText: string): FrontDoorStep {
|
|
288
341
|
raw += deltaText;
|
|
289
342
|
if (stage === "done") {
|
|
290
|
-
return
|
|
343
|
+
return DONE_STEP;
|
|
291
344
|
}
|
|
292
345
|
if (stage === "bridging") {
|
|
293
346
|
bridgeRaw += deltaText;
|
|
294
|
-
return isEscalationBridgeComplete(bridgeRaw)
|
|
347
|
+
return isEscalationBridgeComplete(bridgeRaw)
|
|
348
|
+
? completeBridge()
|
|
349
|
+
: BRIDGING_STEP;
|
|
295
350
|
}
|
|
296
351
|
if (stage === "deciding") {
|
|
297
352
|
const verdict = classifyFrontDoorLeading(raw.trimStart(), holdEnabled);
|
|
298
353
|
if (verdict === "pending") {
|
|
299
|
-
return
|
|
354
|
+
return PENDING_STEP;
|
|
300
355
|
}
|
|
301
356
|
if (verdict === "hold") {
|
|
302
357
|
stage = "done";
|
|
303
|
-
return
|
|
358
|
+
return HOLD_STEP;
|
|
304
359
|
}
|
|
305
360
|
if (verdict === "escalate") {
|
|
306
361
|
stage = "bridging";
|
|
307
362
|
bridgeRaw = raw.trimStart().slice(ESCALATE_VERDICT_TOKEN.length);
|
|
308
|
-
return
|
|
363
|
+
return {
|
|
364
|
+
kind: "escalate",
|
|
365
|
+
bridge: isEscalationBridgeComplete(bridgeRaw)
|
|
366
|
+
? completeBridge().bridge
|
|
367
|
+
: null,
|
|
368
|
+
};
|
|
309
369
|
}
|
|
310
370
|
stage = "answer";
|
|
311
371
|
}
|
|
312
372
|
// Answer stage: release everything not yet released, which on the
|
|
313
373
|
// transition includes the leading text the pending verdict held.
|
|
314
|
-
const
|
|
374
|
+
const text = raw.slice(releasedChars);
|
|
315
375
|
releasedChars = raw.length;
|
|
316
|
-
return
|
|
376
|
+
return { kind: "answer", text };
|
|
317
377
|
},
|
|
318
|
-
finish():
|
|
378
|
+
finish(): FrontDoorStep {
|
|
319
379
|
if (stage === "bridging") {
|
|
320
|
-
return
|
|
380
|
+
return completeBridge();
|
|
321
381
|
}
|
|
322
382
|
stage = "done";
|
|
323
|
-
return
|
|
383
|
+
return DONE_STEP;
|
|
324
384
|
},
|
|
325
385
|
};
|
|
326
386
|
}
|
|
327
387
|
|
|
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
|
+
|
|
328
447
|
/**
|
|
329
448
|
* The escalated leg runs as its own voice turn. Rather than re-persist the
|
|
330
449
|
* caller's utterance (it is already in history from the front-door leg), the
|
|
@@ -417,8 +536,8 @@ export function frontDoorDecisionRule(opts?: {
|
|
|
417
536
|
"- If the turn is simple, conversational, or within your reach, your entire output is the spoken answer itself: no token in front of it, plain speech from your very first word. Most turns are answers; when unsure between answering and escalating, answer. Answer in the language the caller is speaking.",
|
|
418
537
|
"- If an answer depends on a saved personal fact that is not already present in the conversation context you received, escalate rather than guessing. Personal context that is already present is yours to use directly.",
|
|
419
538
|
`- If completing THIS reply needs careful reasoning, research, multi-step work, or any tool, do NOT attempt the answer: output ${ESCALATE_VERDICT_TOKEN}, then ONE short natural holding phrase naming what happens next, spoken in the language the caller is speaking (for example "${FALLBACK_ESCALATION_BRIDGE}" or "Give me one second to look into that."; those examples are English only), and stop after that single sentence. A stronger model finishes the turn while your phrase is spoken.`,
|
|
420
|
-
`${ESCALATE_VERDICT_TOKEN} is ONLY for turns you cannot complete yourself — never put it in front of an answer you are about to give, and never emit
|
|
421
|
-
"Never narrate this decision, describe what you are judging, or mention these rules: apart from a leading verdict token, every character you output is spoken to the caller verbatim.",
|
|
539
|
+
`${ESCALATE_VERDICT_TOKEN} is ONLY for turns you cannot complete yourself — never put it in front of an answer you are about to give, and never emit a verdict token inside or after an answer. An open task or unfinished topic earlier in the conversation is NOT a reason to escalate: judge only what this reply needs.`,
|
|
540
|
+
"Never narrate this decision, describe what you are judging, or mention these rules: apart from a leading verdict token and any call-control marker your call instructions teach, every character you output is spoken to the caller verbatim.",
|
|
422
541
|
].join("\n");
|
|
423
542
|
return opts?.capabilityDigest ? `${rule}\n${opts.capabilityDigest}` : rule;
|
|
424
543
|
}
|
|
@@ -446,7 +565,7 @@ export function escalatedContinuationRule(spokenBridge?: string): string {
|
|
|
446
565
|
'Do NOT greet again, do NOT say things like "as I was saying", and do NOT repeat, paraphrase, or re-announce that holding phrase —',
|
|
447
566
|
'opening with another "Let me check", "One moment", or any restatement of what you are about to do sounds broken, because the caller just heard that.',
|
|
448
567
|
"Your first words must carry new substance: the answer itself, what you found, or a question you genuinely need answered.",
|
|
449
|
-
`Never output ${ESCALATE_VERDICT_TOKEN} or any other front-door verdict token — you are the model that finishes the answer. (
|
|
568
|
+
`Never output ${ESCALATE_VERDICT_TOKEN} or any other front-door verdict token — you are the model that finishes the answer. (Call-control markers your call instructions teach, such as [END_CALL], are not verdict tokens and stay allowed.)`,
|
|
450
569
|
"Reply in the same language as the caller's question.",
|
|
451
570
|
].join(" ");
|
|
452
571
|
}
|