@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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The front-door leg coordinator, shared by the live-voice session and the
|
|
3
|
+
* phone call controller.
|
|
4
|
+
*
|
|
5
|
+
* A triaged voice turn opens on a fast, toolless front-door leg whose
|
|
6
|
+
* leading tokens carry its verdict (see `voice-triage-escalate.ts`). The
|
|
7
|
+
* coordinator reads that leg's raw stream through the verdict machine and
|
|
8
|
+
* sequences what follows: an answer is released as speech, a hold discards
|
|
9
|
+
* a speculative turn, and an escalate verdict hands the turn off to the
|
|
10
|
+
* escalated leg with a spoken bridge. The hand-off order is the part that
|
|
11
|
+
* must not drift between drivers: pause narration, abort the front-door
|
|
12
|
+
* leg, resolve and speak the bridge, mark it as the floor holder, start the
|
|
13
|
+
* escalated leg, re-arm narration. A leg that was cancelled never hands off.
|
|
14
|
+
*
|
|
15
|
+
* Everything transport-specific comes in through {@link FrontDoorLegHost}:
|
|
16
|
+
* how answer text and the bridge are spoken, how a leg is started or
|
|
17
|
+
* aborted, and the speculative hold and commit that only live voice has.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ProgressCadence } from "./voice-progress-cadence.js";
|
|
21
|
+
import {
|
|
22
|
+
createFrontDoorVerdictMachine,
|
|
23
|
+
ESCALATION_CONTINUATION_CONTENT,
|
|
24
|
+
resolveSpokenEscalationBridge,
|
|
25
|
+
} from "./voice-triage-escalate.js";
|
|
26
|
+
|
|
27
|
+
/** The phrase spoken across the hand-off, as {@link resolveSpokenEscalationBridge} settles it. */
|
|
28
|
+
export type SpokenEscalationBridge = ReturnType<
|
|
29
|
+
typeof resolveSpokenEscalationBridge
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
/** The leg that answers for real after the front-door leg handed off. */
|
|
33
|
+
export interface EscalatedLeg {
|
|
34
|
+
content: string;
|
|
35
|
+
routingLeg: "escalated";
|
|
36
|
+
/**
|
|
37
|
+
* The bridge the listener heard, quoted to the escalated continuation
|
|
38
|
+
* rule so the quality model does not re-announce it.
|
|
39
|
+
*/
|
|
40
|
+
spokenEscalationBridge: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The escalated leg for a hand-off whose bridge was `spokenBridge`. The
|
|
45
|
+
* bridge pins this leg to the conversation's own profile, so no profile
|
|
46
|
+
* override rides along.
|
|
47
|
+
*/
|
|
48
|
+
export function escalatedLegFor(spokenBridge: string): EscalatedLeg {
|
|
49
|
+
return {
|
|
50
|
+
content: ESCALATION_CONTINUATION_CONTENT,
|
|
51
|
+
routingLeg: "escalated",
|
|
52
|
+
spokenEscalationBridge: spokenBridge,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The driver's half of a front-door leg. */
|
|
57
|
+
export interface FrontDoorLegHost {
|
|
58
|
+
/**
|
|
59
|
+
* Whether the turn is still live: not cancelled, not finalized. A dead
|
|
60
|
+
* turn must not spawn an escalated leg.
|
|
61
|
+
*/
|
|
62
|
+
isLive(): boolean;
|
|
63
|
+
/** The turn's spoken language, for the canned bridge. */
|
|
64
|
+
language(): string | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Progress narration to pause across the hand-off: the bridge holds the
|
|
67
|
+
* floor, and audible-silence gating keeps narration quiet until it has
|
|
68
|
+
* played on the escalated leg.
|
|
69
|
+
*/
|
|
70
|
+
progress?: Pick<ProgressCadence, "clear" | "noteFloorHolder" | "arm">;
|
|
71
|
+
/**
|
|
72
|
+
* Speculative legs only: the model judged the speaker mid-thought, so the
|
|
73
|
+
* leg is discarded and listening continues.
|
|
74
|
+
*/
|
|
75
|
+
onHold?(): void;
|
|
76
|
+
/**
|
|
77
|
+
* Speculative legs only: commit the turn before acting on an answer or
|
|
78
|
+
* escalate verdict. Returns false when the turn is gone, which stops the
|
|
79
|
+
* verdict from being acted on.
|
|
80
|
+
*/
|
|
81
|
+
commit?(): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Answer text released by the verdict machine, in stream order. On the
|
|
84
|
+
* transition it includes the leading text held while the verdict was
|
|
85
|
+
* pending. The driver runs its own control-marker holdback over it.
|
|
86
|
+
*/
|
|
87
|
+
onAnswerText(text: string): void;
|
|
88
|
+
/**
|
|
89
|
+
* Abort the front-door leg so a model that keeps generating past the
|
|
90
|
+
* bridge cap adds no latency before the escalated leg starts.
|
|
91
|
+
*/
|
|
92
|
+
abortLeg(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Speak the bridge so the strong-model call has no dead air. The model's
|
|
95
|
+
* own bridge is real assistant speech; the canned fallback is audio-only,
|
|
96
|
+
* matching the row the bridge's transcript hygiene deletes for it.
|
|
97
|
+
*/
|
|
98
|
+
speakBridge(bridge: SpokenEscalationBridge): void;
|
|
99
|
+
/** Start the escalated leg on the same turn. */
|
|
100
|
+
startEscalatedLeg(leg: EscalatedLeg): void;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface FrontDoorLegCoordinator {
|
|
104
|
+
/** Whether this leg handed the turn off to the escalated leg. */
|
|
105
|
+
readonly handedOff: boolean;
|
|
106
|
+
/** Feed one raw delta of the front-door leg's stream. */
|
|
107
|
+
push(text: string): void;
|
|
108
|
+
/**
|
|
109
|
+
* The leg completed normally (not cancelled). A leg that stopped
|
|
110
|
+
* mid-bridge (a bare escalate verdict, or a holding phrase with no
|
|
111
|
+
* sentence terminator) hands off now with what arrived; the canned
|
|
112
|
+
* fallback covers an empty bridge. Returns whether the leg handed off.
|
|
113
|
+
*/
|
|
114
|
+
complete(): boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function createFrontDoorLegCoordinator(options: {
|
|
118
|
+
/**
|
|
119
|
+
* Whether the leg's decision rule teaches the hold token: true only for
|
|
120
|
+
* speculative legs, whose leading tokens are interpreted.
|
|
121
|
+
*/
|
|
122
|
+
holdEnabled: boolean;
|
|
123
|
+
host: FrontDoorLegHost;
|
|
124
|
+
}): FrontDoorLegCoordinator {
|
|
125
|
+
const { host } = options;
|
|
126
|
+
const verdict = createFrontDoorVerdictMachine(options.holdEnabled);
|
|
127
|
+
let handedOff = false;
|
|
128
|
+
|
|
129
|
+
const handOff = (cappedBridge: string): void => {
|
|
130
|
+
if (handedOff || !host.isLive()) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
handedOff = true;
|
|
134
|
+
host.progress?.clear();
|
|
135
|
+
host.abortLeg();
|
|
136
|
+
const bridge = resolveSpokenEscalationBridge(cappedBridge, host.language());
|
|
137
|
+
host.speakBridge(bridge);
|
|
138
|
+
// The bridge is the turn's spoken acknowledgement: narration keeps its
|
|
139
|
+
// minimum gap from it rather than following it back to back.
|
|
140
|
+
host.progress?.noteFloorHolder();
|
|
141
|
+
host.startEscalatedLeg(escalatedLegFor(bridge.spokenBridge));
|
|
142
|
+
// The escalated leg runs the slowest work in the system, and the bridge
|
|
143
|
+
// only covers its first couple of seconds: the dead air narration
|
|
144
|
+
// exists for.
|
|
145
|
+
host.progress?.arm();
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
get handedOff() {
|
|
150
|
+
return handedOff;
|
|
151
|
+
},
|
|
152
|
+
push(text) {
|
|
153
|
+
if (handedOff) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const step = verdict.push(text);
|
|
157
|
+
if (step.kind === "hold") {
|
|
158
|
+
host.onHold?.();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (
|
|
162
|
+
(step.kind === "escalate" || step.kind === "answer") &&
|
|
163
|
+
host.commit !== undefined &&
|
|
164
|
+
!host.commit()
|
|
165
|
+
) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (step.kind === "answer") {
|
|
169
|
+
host.onAnswerText(step.text);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (
|
|
173
|
+
(step.kind === "escalate" || step.kind === "bridge") &&
|
|
174
|
+
step.bridge !== null
|
|
175
|
+
) {
|
|
176
|
+
handOff(step.bridge);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
complete() {
|
|
180
|
+
if (!handedOff && host.isLive()) {
|
|
181
|
+
const step = verdict.finish();
|
|
182
|
+
if (step.kind === "bridge") {
|
|
183
|
+
handOff(step.bridge);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return handedOff;
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress narration cadence for one voice turn, shared by the live-voice
|
|
3
|
+
* session and the phone call controller.
|
|
4
|
+
*
|
|
5
|
+
* A voice turn that runs tools is silent while they run. The cadence speaks
|
|
6
|
+
* short audio-only progress updates into that dead air: a generated
|
|
7
|
+
* sentence about what the turn is doing (see `progress-narration.ts`), or a
|
|
8
|
+
* static fallback phrase when generation yields nothing. It follows the
|
|
9
|
+
* turn's tool activity rather than the clock: an "ops" burst, a long
|
|
10
|
+
* operation completing, or a full interval of audible silence with news
|
|
11
|
+
* since the last update, with `maxSilenceMs` as the heartbeat ceiling.
|
|
12
|
+
*
|
|
13
|
+
* The cadence owns the per-turn state (the tool-activity log, epochs,
|
|
14
|
+
* spacing, the self-re-arming idle timer). Everything transport-specific
|
|
15
|
+
* comes in through {@link ProgressCadenceHost}: whether the turn's audio is
|
|
16
|
+
* idle, when it last reached the listener, and how to speak a phrase.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { VoiceProgressConfig } from "../config/schemas/voice.js";
|
|
20
|
+
import { fixedPhraseLanguage } from "../util/language-subtag.js";
|
|
21
|
+
import { getLogger } from "../util/logger.js";
|
|
22
|
+
import type {
|
|
23
|
+
VoiceProgressNarrator,
|
|
24
|
+
VoiceProgressTextInput,
|
|
25
|
+
} from "./progress-narration.js";
|
|
26
|
+
import {
|
|
27
|
+
pickProgressPhrase,
|
|
28
|
+
PROGRESS_FALLBACK_PHRASES_BY_LANGUAGE,
|
|
29
|
+
} from "./progress-phrases.js";
|
|
30
|
+
|
|
31
|
+
const log = getLogger("voice-progress-cadence");
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* One tool operation observed on a turn, fed by the bridge's structured tool
|
|
35
|
+
* callbacks. `completedAtMs`/`isError`/`resultPreview` land with tool_result.
|
|
36
|
+
*/
|
|
37
|
+
export interface TurnProgressOp {
|
|
38
|
+
toolName: string;
|
|
39
|
+
toolUseId?: string;
|
|
40
|
+
startedAtMs: number;
|
|
41
|
+
completedAtMs?: number;
|
|
42
|
+
isError?: boolean;
|
|
43
|
+
resultPreview?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** What prompted a narration attempt. */
|
|
47
|
+
export type ProgressNarrationTrigger = "ops" | "idle" | "op_complete";
|
|
48
|
+
|
|
49
|
+
/** A tool completion as the bridge reports it. */
|
|
50
|
+
export interface ProgressToolResult {
|
|
51
|
+
toolName: string;
|
|
52
|
+
toolUseId?: string;
|
|
53
|
+
isError?: boolean;
|
|
54
|
+
resultPreview?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Newest incomplete op, optionally restricted to a tool name (the
|
|
59
|
+
* tool_result fallback match when no toolUseId correlates).
|
|
60
|
+
*/
|
|
61
|
+
export function findLastIncompleteOp(
|
|
62
|
+
ops: readonly TurnProgressOp[],
|
|
63
|
+
toolName?: string,
|
|
64
|
+
): TurnProgressOp | undefined {
|
|
65
|
+
for (let i = ops.length - 1; i >= 0; i -= 1) {
|
|
66
|
+
const op = ops[i];
|
|
67
|
+
if (
|
|
68
|
+
op.completedAtMs === undefined &&
|
|
69
|
+
(toolName === undefined || op.toolName === toolName)
|
|
70
|
+
) {
|
|
71
|
+
return op;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The transport-specific half of progress narration, supplied by the driver
|
|
79
|
+
* that owns the turn's audio.
|
|
80
|
+
*/
|
|
81
|
+
export interface ProgressCadenceHost {
|
|
82
|
+
/** Identifies the turn in logs. */
|
|
83
|
+
turnId: string;
|
|
84
|
+
/** Dispatch timestamp of the turn (`Date.now()` at launch). */
|
|
85
|
+
launchedAtMs: number;
|
|
86
|
+
/** Aborts an in-flight narration generation with the turn. */
|
|
87
|
+
signal: AbortSignal;
|
|
88
|
+
/**
|
|
89
|
+
* Whether the turn may still be narrated: it is the live turn, it has not
|
|
90
|
+
* completed, and it is not waiting on a decision (a turn waiting on an
|
|
91
|
+
* approval says so once and is quiet after that).
|
|
92
|
+
*/
|
|
93
|
+
canNarrate(): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* No assistant audio is buffered, queued, in flight, or (estimatedly)
|
|
96
|
+
* still playing to the listener.
|
|
97
|
+
*/
|
|
98
|
+
isAudioIdle(): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Wall-clock estimate of when audio already handed to the transport
|
|
101
|
+
* finishes reaching the listener, 0 when nothing is playing. Together with
|
|
102
|
+
* the cadence's own last-emitted stamp this anchors the dead-air countdown
|
|
103
|
+
* to when the listener last heard something.
|
|
104
|
+
*/
|
|
105
|
+
playbackTailUntilMs(): number;
|
|
106
|
+
/** The caller's words this turn answers, for the narrator. */
|
|
107
|
+
transcriptSoFar(): string;
|
|
108
|
+
/** The turn's spoken language, when known. */
|
|
109
|
+
language(): string | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* Bumped on every assistant text delta. A narration generated while the
|
|
112
|
+
* model was already speaking is stale and is dropped.
|
|
113
|
+
*/
|
|
114
|
+
deltaEpoch(): number;
|
|
115
|
+
/**
|
|
116
|
+
* Speak one sanitized phrase, audio-only. `language` is the TTS hint the
|
|
117
|
+
* phrase must carry ("en" for a static fallback the localized table lacks
|
|
118
|
+
* in the turn's language); undefined rides the turn's language. Returns
|
|
119
|
+
* false when nothing was spoken.
|
|
120
|
+
*/
|
|
121
|
+
speak(phrase: string, language: string | undefined): boolean;
|
|
122
|
+
/** Called after a narration was actually enqueued (metrics). */
|
|
123
|
+
onNarrationSpoken?(): void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface ProgressCadenceOptions {
|
|
127
|
+
config: VoiceProgressConfig;
|
|
128
|
+
/** Null disables narration for the turn (no TTS, or narration off). */
|
|
129
|
+
narrator: VoiceProgressNarrator | null;
|
|
130
|
+
host: ProgressCadenceHost;
|
|
131
|
+
/**
|
|
132
|
+
* Rotates the static fallback phrase across turns so a session with
|
|
133
|
+
* repeated dead air does not repeat one sentence.
|
|
134
|
+
*/
|
|
135
|
+
nextFallbackPhraseIndex: () => number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface ProgressCadence {
|
|
139
|
+
/** Tool operations this turn, in start order. */
|
|
140
|
+
readonly ops: readonly TurnProgressOp[];
|
|
141
|
+
/** Narrations actually spoken this turn. */
|
|
142
|
+
readonly updatesSpoken: number;
|
|
143
|
+
/** A tool started: counts toward the "ops" trigger and is news for idle. */
|
|
144
|
+
toolStarted(toolName: string, toolUseId?: string): void;
|
|
145
|
+
/**
|
|
146
|
+
* A tool finished. A long operation completing narrates on its own beat;
|
|
147
|
+
* a quick one only counts as news.
|
|
148
|
+
*/
|
|
149
|
+
toolFinished(event: ProgressToolResult): void;
|
|
150
|
+
/** The turn's audio last finished emitting now: restarts the dead-air clock. */
|
|
151
|
+
noteAudioSettled(): void;
|
|
152
|
+
/**
|
|
153
|
+
* Something else held the floor now (a spoken approval line, a bridge):
|
|
154
|
+
* narration keeps `minGapMs` from it.
|
|
155
|
+
*/
|
|
156
|
+
noteFloorHolder(): void;
|
|
157
|
+
/**
|
|
158
|
+
* Arm (or re-arm) the dead-air timer. A no-op when narration is off for
|
|
159
|
+
* the turn.
|
|
160
|
+
*/
|
|
161
|
+
arm(): void;
|
|
162
|
+
/** Stop the dead-air timer; the turn ended, was cancelled, or handed off. */
|
|
163
|
+
clear(): void;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function createProgressCadence(
|
|
167
|
+
options: ProgressCadenceOptions,
|
|
168
|
+
): ProgressCadence {
|
|
169
|
+
const { config, narrator, host, nextFallbackPhraseIndex } = options;
|
|
170
|
+
const ops: TurnProgressOp[] = [];
|
|
171
|
+
// Ops accumulated toward the next ops-triggered narration. Counted once
|
|
172
|
+
// per op, on start (not completion), so a burst of slow tools still trips
|
|
173
|
+
// the threshold while they run.
|
|
174
|
+
let opsSinceNarration = 0;
|
|
175
|
+
// Bumped by every observable change to the turn's tool activity. The idle
|
|
176
|
+
// trigger compares it against `narratedEpoch` so a tick with nothing new
|
|
177
|
+
// to report stays silent.
|
|
178
|
+
let stateEpoch = 0;
|
|
179
|
+
let narratedEpoch = 0;
|
|
180
|
+
let updatesSpoken = 0;
|
|
181
|
+
// When the last spoken floor-holder enqueued; gates `minGapMs`.
|
|
182
|
+
let lastFloorHolderAtMs: number | null = null;
|
|
183
|
+
// When the turn's audio last finished emitting (turn launch until anything
|
|
184
|
+
// speaks). Idle time is measured from when the listener last heard
|
|
185
|
+
// something, not from when the turn started.
|
|
186
|
+
let lastAudibleAtMs = Date.now();
|
|
187
|
+
let idleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
188
|
+
// A narration generation is awaiting the narrator; serializes narrations.
|
|
189
|
+
let narrationInFlight = false;
|
|
190
|
+
|
|
191
|
+
const enabled = config.enabled && narrator !== null;
|
|
192
|
+
|
|
193
|
+
// When the turn's current audible silence began: the latest of the last
|
|
194
|
+
// emitted segment, the estimated playback end, and the last floor holder.
|
|
195
|
+
const silenceSinceMs = (): number =>
|
|
196
|
+
Math.max(
|
|
197
|
+
lastAudibleAtMs,
|
|
198
|
+
host.playbackTailUntilMs(),
|
|
199
|
+
lastFloorHolderAtMs ?? 0,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const canNarrate = (): boolean => host.canNarrate() && host.isAudioIdle();
|
|
203
|
+
|
|
204
|
+
const withinMinGap = (): boolean =>
|
|
205
|
+
lastFloorHolderAtMs !== null &&
|
|
206
|
+
Date.now() - lastFloorHolderAtMs < config.minGapMs;
|
|
207
|
+
|
|
208
|
+
// The idle tick has something worth saying when the turn's tool activity
|
|
209
|
+
// has moved since the last narration described it, or when the silence has
|
|
210
|
+
// run past `maxSilenceMs`, the heartbeat ceiling that proves the assistant
|
|
211
|
+
// is still alive on a turn with no observable activity at all.
|
|
212
|
+
const idleHasSomethingToSay = (): boolean => {
|
|
213
|
+
if (stateEpoch !== narratedEpoch) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
const silentForMs = Date.now() - silenceSinceMs();
|
|
217
|
+
if (silentForMs >= config.maxSilenceMs) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
log.debug(
|
|
221
|
+
{ turnId: host.turnId, silentForMs },
|
|
222
|
+
"Voice progress narration held: nothing new since the last update",
|
|
223
|
+
);
|
|
224
|
+
return false;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const clearTimer = (): void => {
|
|
228
|
+
if (idleTimer !== null) {
|
|
229
|
+
clearTimeout(idleTimer);
|
|
230
|
+
idleTimer = null;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Arms the dead-air timer. The countdown measures audible silence, not
|
|
235
|
+
// time since launch, so it covers mid-turn silences for the whole turn. On
|
|
236
|
+
// expiry with audio still pending, or with the silence not yet a full
|
|
237
|
+
// interval old, it re-arms for the remainder; only a full interval of
|
|
238
|
+
// audible silence reaches the gatekeeper. The interval is a polling
|
|
239
|
+
// cadence, not a speaking cadence: most ticks find nothing new and stay
|
|
240
|
+
// quiet.
|
|
241
|
+
const armTimer = (delayMs?: number): void => {
|
|
242
|
+
clearTimer();
|
|
243
|
+
idleTimer = setTimeout(() => {
|
|
244
|
+
idleTimer = null;
|
|
245
|
+
if (!host.canNarrate()) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (!host.isAudioIdle()) {
|
|
249
|
+
armTimer();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const remaining = silenceSinceMs() + config.idleIntervalMs - Date.now();
|
|
253
|
+
if (remaining > 0) {
|
|
254
|
+
armTimer(remaining);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
maybeNarrate("idle");
|
|
258
|
+
armTimer();
|
|
259
|
+
}, delayMs ?? config.idleIntervalMs);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Generate and speak one audio-only progress narration. On a null result,
|
|
263
|
+
// idle narration falls back to a static phrase while activity-triggered
|
|
264
|
+
// narration stays silent.
|
|
265
|
+
const speakUpdate = async (
|
|
266
|
+
activeNarrator: VoiceProgressNarrator,
|
|
267
|
+
trigger: ProgressNarrationTrigger,
|
|
268
|
+
): Promise<void> => {
|
|
269
|
+
narrationInFlight = true;
|
|
270
|
+
const deltaEpochAtLaunch = host.deltaEpoch();
|
|
271
|
+
// The activity this update describes. Tool events that land
|
|
272
|
+
// mid-generation are news the generated text cannot carry, so they must
|
|
273
|
+
// leave the idle trigger armed rather than count as already narrated.
|
|
274
|
+
const stateEpochAtLaunch = stateEpoch;
|
|
275
|
+
try {
|
|
276
|
+
const now = Date.now();
|
|
277
|
+
const currentOp = findLastIncompleteOp(ops);
|
|
278
|
+
const language = host.language();
|
|
279
|
+
const input: VoiceProgressTextInput = {
|
|
280
|
+
transcriptSoFar: host.transcriptSoFar(),
|
|
281
|
+
completedOps: ops
|
|
282
|
+
.filter(
|
|
283
|
+
(op): op is TurnProgressOp & { completedAtMs: number } =>
|
|
284
|
+
op.completedAtMs !== undefined,
|
|
285
|
+
)
|
|
286
|
+
// `ops` is in start order; the narrator wants completion order,
|
|
287
|
+
// which differs when parallel tools finish out of order.
|
|
288
|
+
.sort((a, b) => a.completedAtMs - b.completedAtMs)
|
|
289
|
+
.map((op) => ({
|
|
290
|
+
toolName: op.toolName,
|
|
291
|
+
...(op.isError !== undefined ? { isError: op.isError } : {}),
|
|
292
|
+
...(op.resultPreview !== undefined
|
|
293
|
+
? { resultPreview: op.resultPreview }
|
|
294
|
+
: {}),
|
|
295
|
+
})),
|
|
296
|
+
currentOp: currentOp
|
|
297
|
+
? {
|
|
298
|
+
toolName: currentOp.toolName,
|
|
299
|
+
elapsedMs: now - currentOp.startedAtMs,
|
|
300
|
+
}
|
|
301
|
+
: null,
|
|
302
|
+
turnElapsedMs: now - host.launchedAtMs,
|
|
303
|
+
updateIndex: updatesSpoken + 1,
|
|
304
|
+
...(language !== undefined ? { languageHint: language } : {}),
|
|
305
|
+
};
|
|
306
|
+
const generated = await activeNarrator
|
|
307
|
+
.generateProgressText(input, host.signal)
|
|
308
|
+
// The narrator contract never rejects. Keep test stubs fail-soft too.
|
|
309
|
+
.catch(() => null);
|
|
310
|
+
// Re-check liveness and staleness after the provider call.
|
|
311
|
+
if (
|
|
312
|
+
!canNarrate() ||
|
|
313
|
+
host.deltaEpoch() !== deltaEpochAtLaunch ||
|
|
314
|
+
withinMinGap()
|
|
315
|
+
) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
let raw = generated;
|
|
319
|
+
// Generated text is in the turn's language; only the static fallback
|
|
320
|
+
// comes from a localized table and may need the "en" override.
|
|
321
|
+
let fillerLanguage: string | undefined;
|
|
322
|
+
if (raw === null) {
|
|
323
|
+
if (trigger !== "idle") {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
raw = pickProgressPhrase(nextFallbackPhraseIndex(), language);
|
|
327
|
+
fillerLanguage = fixedPhraseLanguage(
|
|
328
|
+
PROGRESS_FALLBACK_PHRASES_BY_LANGUAGE,
|
|
329
|
+
language,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
if (!host.speak(raw, fillerLanguage)) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
lastFloorHolderAtMs = Date.now();
|
|
336
|
+
opsSinceNarration = 0;
|
|
337
|
+
narratedEpoch = stateEpochAtLaunch;
|
|
338
|
+
updatesSpoken += 1;
|
|
339
|
+
host.onNarrationSpoken?.();
|
|
340
|
+
// Restart the dead-air countdown from this narration.
|
|
341
|
+
armTimer();
|
|
342
|
+
} finally {
|
|
343
|
+
narrationInFlight = false;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// Gatekeeper. Speaks only while the turn is audibly silent, with one
|
|
348
|
+
// generation at a time and the configured spacing.
|
|
349
|
+
const maybeNarrate = (trigger: ProgressNarrationTrigger): void => {
|
|
350
|
+
if (
|
|
351
|
+
!enabled ||
|
|
352
|
+
narrator === null ||
|
|
353
|
+
!canNarrate() ||
|
|
354
|
+
narrationInFlight ||
|
|
355
|
+
withinMinGap() ||
|
|
356
|
+
(trigger === "ops" && opsSinceNarration < config.opsThreshold) ||
|
|
357
|
+
(trigger === "idle" && !idleHasSomethingToSay())
|
|
358
|
+
) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
void speakUpdate(narrator, trigger);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
get ops() {
|
|
366
|
+
return ops;
|
|
367
|
+
},
|
|
368
|
+
get updatesSpoken() {
|
|
369
|
+
return updatesSpoken;
|
|
370
|
+
},
|
|
371
|
+
toolStarted(toolName, toolUseId) {
|
|
372
|
+
ops.push({
|
|
373
|
+
toolName,
|
|
374
|
+
...(toolUseId !== undefined ? { toolUseId } : {}),
|
|
375
|
+
startedAtMs: Date.now(),
|
|
376
|
+
});
|
|
377
|
+
opsSinceNarration += 1;
|
|
378
|
+
stateEpoch += 1;
|
|
379
|
+
maybeNarrate("ops");
|
|
380
|
+
},
|
|
381
|
+
toolFinished(event) {
|
|
382
|
+
// Match by toolUseId when present; otherwise the last-started
|
|
383
|
+
// incomplete op with the same name (parallel same-name ops resolve
|
|
384
|
+
// newest-first).
|
|
385
|
+
const op =
|
|
386
|
+
(event.toolUseId !== undefined
|
|
387
|
+
? ops.find((o) => o.toolUseId === event.toolUseId)
|
|
388
|
+
: undefined) ?? findLastIncompleteOp(ops, event.toolName);
|
|
389
|
+
// A long-running op finishing is the beat the listener has been
|
|
390
|
+
// waiting through: it narrates immediately rather than waiting for
|
|
391
|
+
// `opsThreshold` more ops, which on a one-slow-tool turn never arrive.
|
|
392
|
+
// Short ops stay on the ops trigger.
|
|
393
|
+
let trigger: ProgressNarrationTrigger = "ops";
|
|
394
|
+
if (op) {
|
|
395
|
+
op.completedAtMs = Date.now();
|
|
396
|
+
if (event.isError !== undefined) {
|
|
397
|
+
op.isError = event.isError;
|
|
398
|
+
}
|
|
399
|
+
if (event.resultPreview !== undefined) {
|
|
400
|
+
op.resultPreview = event.resultPreview;
|
|
401
|
+
}
|
|
402
|
+
if (op.completedAtMs - op.startedAtMs >= config.longOpMs) {
|
|
403
|
+
trigger = "op_complete";
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
stateEpoch += 1;
|
|
407
|
+
maybeNarrate(trigger);
|
|
408
|
+
},
|
|
409
|
+
noteAudioSettled() {
|
|
410
|
+
lastAudibleAtMs = Date.now();
|
|
411
|
+
},
|
|
412
|
+
noteFloorHolder() {
|
|
413
|
+
lastFloorHolderAtMs = Date.now();
|
|
414
|
+
},
|
|
415
|
+
arm() {
|
|
416
|
+
if (!enabled) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
armTimer();
|
|
420
|
+
},
|
|
421
|
+
clear: clearTimer,
|
|
422
|
+
};
|
|
423
|
+
}
|