@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
|
@@ -459,6 +459,28 @@ describe("feed-writer", () => {
|
|
|
459
459
|
expect(result!.summary).toBe("Fresh summary");
|
|
460
460
|
});
|
|
461
461
|
|
|
462
|
+
test("a status value replaces, and a status updater reads write-time state", async () => {
|
|
463
|
+
await appendFeedItem(makeItem({ id: "item-1" }));
|
|
464
|
+
|
|
465
|
+
const replaced = await patchFeedItemContent("item-1", {
|
|
466
|
+
status: "acted_on",
|
|
467
|
+
});
|
|
468
|
+
expect(replaced!.status).toBe("acted_on");
|
|
469
|
+
|
|
470
|
+
// The updater sees the status as of the write, so a conditional
|
|
471
|
+
// transition cannot overwrite what another writer just set.
|
|
472
|
+
const observed: string[] = [];
|
|
473
|
+
const conditional = await patchFeedItemContent("item-1", {
|
|
474
|
+
status: (existing) => {
|
|
475
|
+
observed.push(existing.status);
|
|
476
|
+
return existing.status === "new" ? "seen" : existing.status;
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
expect(observed).toEqual(["acted_on"]);
|
|
480
|
+
expect(conditional!.status).toBe("acted_on");
|
|
481
|
+
expect(readFileJson().items[0]!.status).toBe("acted_on");
|
|
482
|
+
});
|
|
483
|
+
|
|
462
484
|
test("returns null for an unknown id", async () => {
|
|
463
485
|
await appendFeedItem(makeItem({ id: "known" }));
|
|
464
486
|
expect(await patchFeedItemContent("unknown", { title: "x" })).toBeNull();
|
package/src/home/feed-writer.ts
CHANGED
|
@@ -185,7 +185,13 @@ export interface FeedItemContentPatch {
|
|
|
185
185
|
title?: string;
|
|
186
186
|
summary?: string;
|
|
187
187
|
urgency?: FeedItemUrgency;
|
|
188
|
-
|
|
188
|
+
/**
|
|
189
|
+
* New status, or an updater applied inside the coalescing queue so it
|
|
190
|
+
* reads the item as of write time. Use the updater for a conditional
|
|
191
|
+
* transition: a plain value would overwrite a status another writer
|
|
192
|
+
* set while the patch sat in the queue.
|
|
193
|
+
*/
|
|
194
|
+
status?: FeedItemStatus | ((existing: FeedItem) => FeedItemStatus);
|
|
189
195
|
/**
|
|
190
196
|
* Updater for the item's guardian projection, applied inside the
|
|
191
197
|
* coalescing queue so it reads the projection as of write time (a
|
|
@@ -399,7 +405,10 @@ async function runWrite(): Promise<void> {
|
|
|
399
405
|
updated.urgency = patch.urgency;
|
|
400
406
|
}
|
|
401
407
|
if (patch.status !== undefined) {
|
|
402
|
-
updated.status =
|
|
408
|
+
updated.status =
|
|
409
|
+
typeof patch.status === "function"
|
|
410
|
+
? patch.status(existing)
|
|
411
|
+
: patch.status;
|
|
403
412
|
}
|
|
404
413
|
if (patch.guardianRequest !== undefined && existing.guardianRequest) {
|
|
405
414
|
updated.guardianRequest = patch.guardianRequest(existing.guardianRequest);
|
package/src/hooks/types.ts
CHANGED
|
@@ -679,3 +679,36 @@ export interface ConversationDeletedContext
|
|
|
679
679
|
* {@link BaseHookContext}.
|
|
680
680
|
*/
|
|
681
681
|
export type ConversationsClearedContext = BaseHookContext;
|
|
682
|
+
|
|
683
|
+
// ─── Message-deleted hook context ────────────────────────────────────────────
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Context passed to the `message-deleted` hook. Fires once per deleted
|
|
687
|
+
* message row, from the shared single-message delete primitive, after the row
|
|
688
|
+
* is removed. Whole-conversation deletes dispatch `conversation-deleted`
|
|
689
|
+
* instead.
|
|
690
|
+
*
|
|
691
|
+
* Fire-and-forget like `conversation-deleted`: the primitive is synchronous
|
|
692
|
+
* and does not wait for the chain. The row is gone by the time a hook runs,
|
|
693
|
+
* so the context carries the row's `createdAt` alongside its id. Together
|
|
694
|
+
* they are the `(createdAt, id)` position the row held, which is what a hook
|
|
695
|
+
* keeping a cursor on that row needs to record once the row can no longer be
|
|
696
|
+
* looked up. The default memory plugin contributes a hook here that stamps
|
|
697
|
+
* its retrospective cursor.
|
|
698
|
+
*/
|
|
699
|
+
export interface MessageDeletedInputContext {
|
|
700
|
+
/** ID of the conversation that owned the row. */
|
|
701
|
+
readonly conversationId: string;
|
|
702
|
+
/** ID of the deleted message row. */
|
|
703
|
+
readonly messageId: string;
|
|
704
|
+
/** The deleted row's `createdAt`. */
|
|
705
|
+
readonly createdAt: number;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* The full `message-deleted` context a hook receives: the dispatching call
|
|
710
|
+
* site's {@link MessageDeletedInputContext} plus the pipeline-stamped
|
|
711
|
+
* {@link BaseHookContext} capabilities.
|
|
712
|
+
*/
|
|
713
|
+
export interface MessageDeletedContext
|
|
714
|
+
extends MessageDeletedInputContext, BaseHookContext {}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Daemon i18n
|
|
2
|
+
|
|
3
|
+
User-facing copy the assistant generates as a hardcoded constant goes
|
|
4
|
+
through `assistant/src/i18n/`. The web app has its own catalogs. This
|
|
5
|
+
module is for constants the daemon writes or emits.
|
|
6
|
+
|
|
7
|
+
## Rules
|
|
8
|
+
|
|
9
|
+
1. Add a key to `MESSAGE_KEYS` and to every locale in `MESSAGE_CATALOGS`.
|
|
10
|
+
2. Persist the key when a column holds a system constant. Never persist a
|
|
11
|
+
translated string.
|
|
12
|
+
3. Resolve with `t(key, locale)` or a typed helper (`resolveConversationTitle`)
|
|
13
|
+
at the edge that emits the string.
|
|
14
|
+
4. Do not match stored display strings back to keys. A stored title may be
|
|
15
|
+
in any language on purpose.
|
|
16
|
+
5. Locale comes from `Accept-Language` (`localeFromAcceptLanguage`) or an
|
|
17
|
+
explicit CLI flag. Default `en` lives in this module, not at each caller.
|
|
18
|
+
|
|
19
|
+
Conversation titles persist `conversation.title.generating` /
|
|
20
|
+
`conversation.title.untitled`. `resolveConversationTitle` turns those keys
|
|
21
|
+
into catalog copy. Legacy English placeholders stay replaceable via the
|
|
22
|
+
title service patterns and pass through on the wire until they are retitled.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_LOCALE,
|
|
5
|
+
isMessageKey,
|
|
6
|
+
localeFromAcceptLanguage,
|
|
7
|
+
MESSAGE_CATALOGS,
|
|
8
|
+
MESSAGE_KEYS,
|
|
9
|
+
negotiateLocale,
|
|
10
|
+
resolveConversationTitle,
|
|
11
|
+
SUPPORTED_LOCALES,
|
|
12
|
+
t,
|
|
13
|
+
} from "../index.js";
|
|
14
|
+
|
|
15
|
+
describe("negotiateLocale", () => {
|
|
16
|
+
test("matches a full shipped tag", () => {
|
|
17
|
+
expect(negotiateLocale(["es"])).toBe("es");
|
|
18
|
+
expect(negotiateLocale(["zh-TW"])).toBe("zh-TW");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("falls back from a regional tag to the primary language", () => {
|
|
22
|
+
expect(negotiateLocale(["es-MX", "en"])).toBe("es");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("maps Traditional Chinese tags to zh-TW", () => {
|
|
26
|
+
expect(negotiateLocale(["zh-Hant"])).toBe("zh-TW");
|
|
27
|
+
expect(negotiateLocale(["zh-HK"])).toBe("zh-TW");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("defaults when nothing matches", () => {
|
|
31
|
+
expect(negotiateLocale([])).toBe(DEFAULT_LOCALE);
|
|
32
|
+
expect(negotiateLocale(["xx"])).toBe(DEFAULT_LOCALE);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("localeFromAcceptLanguage", () => {
|
|
37
|
+
test("honors quality values", () => {
|
|
38
|
+
expect(localeFromAcceptLanguage("fr;q=0.8, es;q=0.9, en;q=0.5")).toBe(
|
|
39
|
+
"es",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("defaults on missing or empty", () => {
|
|
44
|
+
expect(localeFromAcceptLanguage(null)).toBe(DEFAULT_LOCALE);
|
|
45
|
+
expect(localeFromAcceptLanguage("")).toBe(DEFAULT_LOCALE);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("catalog completeness", () => {
|
|
50
|
+
test("every locale has every key", () => {
|
|
51
|
+
const keys = Object.values(MESSAGE_KEYS);
|
|
52
|
+
for (const locale of SUPPORTED_LOCALES) {
|
|
53
|
+
for (const key of keys) {
|
|
54
|
+
expect(MESSAGE_CATALOGS[locale][key].length).toBeGreaterThan(0);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("t", () => {
|
|
61
|
+
test("resolves generating and untitled per locale", () => {
|
|
62
|
+
expect(t(MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING, "en")).toBe(
|
|
63
|
+
"Generating title...",
|
|
64
|
+
);
|
|
65
|
+
expect(t(MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED, "en")).toBe(
|
|
66
|
+
"Untitled Conversation",
|
|
67
|
+
);
|
|
68
|
+
expect(t(MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED, "es")).toBe(
|
|
69
|
+
"Sin título",
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("resolveConversationTitle", () => {
|
|
75
|
+
test("resolves a stored key and an empty title", () => {
|
|
76
|
+
expect(
|
|
77
|
+
resolveConversationTitle(MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING, "zh"),
|
|
78
|
+
).toBe("标题生成中...");
|
|
79
|
+
expect(resolveConversationTitle(null, "es")).toBe("Sin título");
|
|
80
|
+
expect(resolveConversationTitle("")).toBe("Untitled Conversation");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("passes stored display strings through, including English placeholders", () => {
|
|
84
|
+
expect(resolveConversationTitle("Untitled Conversation", "es")).toBe(
|
|
85
|
+
"Untitled Conversation",
|
|
86
|
+
);
|
|
87
|
+
expect(resolveConversationTitle("Generating title...", "zh")).toBe(
|
|
88
|
+
"Generating title...",
|
|
89
|
+
);
|
|
90
|
+
expect(resolveConversationTitle("Auth Middleware Rewrite", "es")).toBe(
|
|
91
|
+
"Auth Middleware Rewrite",
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("defaults locale inside the helper", () => {
|
|
96
|
+
expect(
|
|
97
|
+
resolveConversationTitle(MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING),
|
|
98
|
+
).toBe("Generating title...");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("isMessageKey", () => {
|
|
103
|
+
test("recognizes catalog keys only", () => {
|
|
104
|
+
expect(isMessageKey(MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED)).toBe(true);
|
|
105
|
+
expect(isMessageKey("Untitled Conversation")).toBe(false);
|
|
106
|
+
expect(isMessageKey("Untitled")).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export {
|
|
2
|
+
DEFAULT_LOCALE,
|
|
3
|
+
isSupportedLocale,
|
|
4
|
+
localeFromAcceptLanguage,
|
|
5
|
+
negotiateLocale,
|
|
6
|
+
SUPPORTED_LOCALES,
|
|
7
|
+
type SupportedLocale,
|
|
8
|
+
} from "./locales.js";
|
|
9
|
+
export {
|
|
10
|
+
isMessageKey,
|
|
11
|
+
MESSAGE_CATALOGS,
|
|
12
|
+
MESSAGE_KEYS,
|
|
13
|
+
type MessageKey,
|
|
14
|
+
t,
|
|
15
|
+
} from "./messages.js";
|
|
16
|
+
export { resolveConversationTitle } from "./title.js";
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locales the daemon can resolve user-facing copy into.
|
|
3
|
+
*
|
|
4
|
+
* Keep this set aligned with `clients/web/src/i18n/supported-locales.ts`.
|
|
5
|
+
* A locale that exists only here cannot be requested by the web app; a
|
|
6
|
+
* locale that exists only there cannot be resolved for CLI or HTTP
|
|
7
|
+
* consumers that go through this module.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_LOCALE = "en";
|
|
11
|
+
|
|
12
|
+
export const SUPPORTED_LOCALES = ["en", "es", "ru", "zh", "zh-TW"] as const;
|
|
13
|
+
|
|
14
|
+
export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
|
|
15
|
+
|
|
16
|
+
export function isSupportedLocale(value: unknown): value is SupportedLocale {
|
|
17
|
+
return (
|
|
18
|
+
typeof value === "string" &&
|
|
19
|
+
(SUPPORTED_LOCALES as readonly string[]).includes(value)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Pick the best shipped locale for an ordered list of preferred BCP 47 tags.
|
|
25
|
+
*
|
|
26
|
+
* Full tag first, then the primary language subtag, so `es-MX` resolves to
|
|
27
|
+
* `es`. `zh-Hant` / `zh-HK` / `zh-MO` map to `zh-TW`.
|
|
28
|
+
*/
|
|
29
|
+
export function negotiateLocale(preferred: readonly string[]): SupportedLocale {
|
|
30
|
+
for (const tag of preferred) {
|
|
31
|
+
const normalized = tag.trim();
|
|
32
|
+
if (normalized === "") {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const exact = SUPPORTED_LOCALES.find(
|
|
36
|
+
(locale) => locale.toLowerCase() === normalized.toLowerCase(),
|
|
37
|
+
);
|
|
38
|
+
if (exact) {
|
|
39
|
+
return exact;
|
|
40
|
+
}
|
|
41
|
+
const lower = normalized.toLowerCase();
|
|
42
|
+
if (
|
|
43
|
+
lower === "zh-hant" ||
|
|
44
|
+
lower.startsWith("zh-hant-") ||
|
|
45
|
+
lower === "zh-hk" ||
|
|
46
|
+
lower === "zh-mo"
|
|
47
|
+
) {
|
|
48
|
+
return "zh-TW";
|
|
49
|
+
}
|
|
50
|
+
const base = normalized.split("-")[0]?.toLowerCase();
|
|
51
|
+
const baseMatched = SUPPORTED_LOCALES.find(
|
|
52
|
+
(locale) => locale.toLowerCase() === base,
|
|
53
|
+
);
|
|
54
|
+
if (baseMatched) {
|
|
55
|
+
return baseMatched;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return DEFAULT_LOCALE;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse an `Accept-Language` header into preference order and negotiate.
|
|
63
|
+
*
|
|
64
|
+
* Quality values are honored; the header is a hint, not a required input.
|
|
65
|
+
* Missing or empty headers resolve to {@link DEFAULT_LOCALE}.
|
|
66
|
+
*/
|
|
67
|
+
export function localeFromAcceptLanguage(
|
|
68
|
+
header: string | null | undefined,
|
|
69
|
+
): SupportedLocale {
|
|
70
|
+
if (header == null || header.trim() === "") {
|
|
71
|
+
return DEFAULT_LOCALE;
|
|
72
|
+
}
|
|
73
|
+
const tags = header
|
|
74
|
+
.split(",")
|
|
75
|
+
.map((part) => {
|
|
76
|
+
const [tag, ...params] = part.trim().split(";");
|
|
77
|
+
let quality = 1;
|
|
78
|
+
for (const param of params) {
|
|
79
|
+
const [name, value] = param.trim().split("=");
|
|
80
|
+
if (name === "q" && value !== undefined) {
|
|
81
|
+
const parsed = Number.parseFloat(value);
|
|
82
|
+
if (Number.isFinite(parsed)) {
|
|
83
|
+
quality = parsed;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { tag: tag?.trim() ?? "", quality };
|
|
88
|
+
})
|
|
89
|
+
.filter((entry) => entry.tag.length > 0)
|
|
90
|
+
.sort((a, b) => b.quality - a.quality)
|
|
91
|
+
.map((entry) => entry.tag);
|
|
92
|
+
return negotiateLocale(tags);
|
|
93
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon message catalog.
|
|
3
|
+
*
|
|
4
|
+
* Keys are the stable identity of a hardcoded constant. English is the
|
|
5
|
+
* source language. Add a key here, then add the same key to every locale
|
|
6
|
+
* catalog. A key present in a translated catalog but missing from English
|
|
7
|
+
* is a test failure.
|
|
8
|
+
*
|
|
9
|
+
* Persist the key when a column holds a system constant. Never persist a
|
|
10
|
+
* translated string, and never treat a stored display string as a key:
|
|
11
|
+
* a user title may be in any language.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_LOCALE,
|
|
16
|
+
type SupportedLocale,
|
|
17
|
+
} from "./locales.js";
|
|
18
|
+
|
|
19
|
+
export const MESSAGE_KEYS = {
|
|
20
|
+
CONVERSATION_TITLE_GENERATING: "conversation.title.generating",
|
|
21
|
+
CONVERSATION_TITLE_UNTITLED: "conversation.title.untitled",
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
export type MessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];
|
|
25
|
+
|
|
26
|
+
export const MESSAGE_CATALOGS: Record<
|
|
27
|
+
SupportedLocale,
|
|
28
|
+
Record<MessageKey, string>
|
|
29
|
+
> = {
|
|
30
|
+
en: {
|
|
31
|
+
"conversation.title.generating": "Generating title...",
|
|
32
|
+
"conversation.title.untitled": "Untitled Conversation",
|
|
33
|
+
},
|
|
34
|
+
es: {
|
|
35
|
+
"conversation.title.generating": "Generando título...",
|
|
36
|
+
"conversation.title.untitled": "Sin título",
|
|
37
|
+
},
|
|
38
|
+
ru: {
|
|
39
|
+
"conversation.title.generating": "Создание названия...",
|
|
40
|
+
"conversation.title.untitled": "Без названия",
|
|
41
|
+
},
|
|
42
|
+
zh: {
|
|
43
|
+
"conversation.title.generating": "标题生成中...",
|
|
44
|
+
"conversation.title.untitled": "无标题",
|
|
45
|
+
},
|
|
46
|
+
"zh-TW": {
|
|
47
|
+
"conversation.title.generating": "標題產生中...",
|
|
48
|
+
"conversation.title.untitled": "未命名",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function t(
|
|
53
|
+
key: MessageKey,
|
|
54
|
+
locale: SupportedLocale = DEFAULT_LOCALE,
|
|
55
|
+
): string {
|
|
56
|
+
return MESSAGE_CATALOGS[locale][key] ?? MESSAGE_CATALOGS[DEFAULT_LOCALE][key];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isMessageKey(value: string): value is MessageKey {
|
|
60
|
+
return (Object.values(MESSAGE_KEYS) as string[]).includes(value);
|
|
61
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a conversation title for a locale.
|
|
3
|
+
*
|
|
4
|
+
* A stored message key (or an empty title) is a hardcoded constant and
|
|
5
|
+
* resolves through the catalog. Any other stored string is user or model
|
|
6
|
+
* copy and passes through, including English placeholders written before
|
|
7
|
+
* keys were persisted.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { DEFAULT_LOCALE, type SupportedLocale } from "./locales.js";
|
|
11
|
+
import { isMessageKey, MESSAGE_KEYS, t } from "./messages.js";
|
|
12
|
+
|
|
13
|
+
export function resolveConversationTitle(
|
|
14
|
+
title: string | null | undefined,
|
|
15
|
+
locale: SupportedLocale = DEFAULT_LOCALE,
|
|
16
|
+
): string {
|
|
17
|
+
const trimmed = title?.trim() ?? "";
|
|
18
|
+
if (trimmed === "") {
|
|
19
|
+
return t(MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED, locale);
|
|
20
|
+
}
|
|
21
|
+
if (isMessageKey(trimmed)) {
|
|
22
|
+
return t(trimmed, locale);
|
|
23
|
+
}
|
|
24
|
+
return trimmed;
|
|
25
|
+
}
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
* the platform can register a route that points at this instance.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { getPlatformBaseUrl } from "../config/env.js";
|
|
22
22
|
import { getIsPlatform } from "../config/env-registry.js";
|
|
23
23
|
import { getConfig } from "../config/loader.js";
|
|
24
|
+
import { resolvePlatformAssistantId } from "../config/platform-identity.js";
|
|
24
25
|
import { ipcRegisterWebhookRoute } from "../ipc/gateway-client.js";
|
|
25
26
|
import { credentialKey } from "../security/credential-key.js";
|
|
26
27
|
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
@@ -45,11 +46,11 @@ export interface PlatformCallbackRegistrationContext {
|
|
|
45
46
|
|
|
46
47
|
export async function resolvePlatformCallbackRegistrationContext(): Promise<PlatformCallbackRegistrationContext> {
|
|
47
48
|
const platform = getIsPlatform();
|
|
48
|
-
const [storedBaseUrlRaw,
|
|
49
|
+
const [storedBaseUrlRaw, storedAssistantApiKeyRaw, assistantId] =
|
|
49
50
|
await Promise.all([
|
|
50
51
|
getSecureKeyAsync(credentialKey("vellum", "platform_base_url")),
|
|
51
|
-
getSecureKeyAsync(credentialKey("vellum", "platform_assistant_id")),
|
|
52
52
|
getSecureKeyAsync(credentialKey("vellum", "assistant_api_key")),
|
|
53
|
+
resolvePlatformAssistantId(),
|
|
53
54
|
]);
|
|
54
55
|
|
|
55
56
|
const storedBaseUrl = storedBaseUrlRaw?.trim();
|
|
@@ -57,8 +58,6 @@ export async function resolvePlatformCallbackRegistrationContext(): Promise<Plat
|
|
|
57
58
|
/\/+$/,
|
|
58
59
|
"",
|
|
59
60
|
);
|
|
60
|
-
const assistantId =
|
|
61
|
-
getPlatformAssistantId().trim() || storedAssistantIdRaw?.trim() || "";
|
|
62
61
|
const envAssistantCredential = process.env.ASSISTANT_API_KEY?.trim();
|
|
63
62
|
const assistantCredential =
|
|
64
63
|
storedAssistantApiKeyRaw?.trim() || envAssistantCredential || undefined;
|
|
@@ -980,3 +980,124 @@ describe("LiveVoiceSession room reveal", () => {
|
|
|
980
980
|
expect(ttsTexts.join(" ")).not.toContain("ASK_GUARDIAN_APPROVAL");
|
|
981
981
|
});
|
|
982
982
|
});
|
|
983
|
+
|
|
984
|
+
describe("LiveVoiceSession spoken session controls", () => {
|
|
985
|
+
function createControlsHarness(
|
|
986
|
+
sessionControls?: LiveVoiceClientStartFrame["sessionControls"],
|
|
987
|
+
) {
|
|
988
|
+
const { startVoiceTurn, getCallbacks } = createCapturingTurnStarter();
|
|
989
|
+
const { streamTtsAudio, ttsTexts } = createRecordingTtsStreamer();
|
|
990
|
+
const harness = createSessionHarness({
|
|
991
|
+
startVoiceTurn,
|
|
992
|
+
streamTtsAudio,
|
|
993
|
+
startFrame: {
|
|
994
|
+
...START_FRAME,
|
|
995
|
+
...(sessionControls ? { sessionControls } : {}),
|
|
996
|
+
},
|
|
997
|
+
});
|
|
998
|
+
return { ...harness, getCallbacks, ttsTexts };
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
test("a reply ending with [END_CALL] sends an end control after tts_done", async () => {
|
|
1002
|
+
const { frames, session, getCallbacks, ttsTexts } = createControlsHarness([
|
|
1003
|
+
"end",
|
|
1004
|
+
"mute",
|
|
1005
|
+
]);
|
|
1006
|
+
|
|
1007
|
+
await startReleasedTurn(session, getCallbacks);
|
|
1008
|
+
emitTextDelta(getCallbacks, "Okay, talk soon. [END_CALL]");
|
|
1009
|
+
emitMessageComplete(getCallbacks);
|
|
1010
|
+
await waitFor(() =>
|
|
1011
|
+
frames.some((frame) => frame.type === "session_control"),
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
const ttsDoneIndex = frames.findIndex((frame) => frame.type === "tts_done");
|
|
1015
|
+
const controlIndex = frames.findIndex(
|
|
1016
|
+
(frame) => frame.type === "session_control",
|
|
1017
|
+
);
|
|
1018
|
+
// The goodbye is heard before the call ends.
|
|
1019
|
+
expect(controlIndex).toBeGreaterThan(ttsDoneIndex);
|
|
1020
|
+
expect(frames[controlIndex]).toMatchObject({
|
|
1021
|
+
type: "session_control",
|
|
1022
|
+
turnId: "live-turn-1",
|
|
1023
|
+
action: "end",
|
|
1024
|
+
});
|
|
1025
|
+
expect(assistantDeltaTexts(frames).join("").trim()).toBe(
|
|
1026
|
+
"Okay, talk soon.",
|
|
1027
|
+
);
|
|
1028
|
+
expect(ttsTexts.join(" ")).not.toContain("[END_CALL]");
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
test("a timed mute carries its duration", async () => {
|
|
1032
|
+
const { frames, session, getCallbacks } = createControlsHarness(["mute"]);
|
|
1033
|
+
|
|
1034
|
+
await startReleasedTurn(session, getCallbacks);
|
|
1035
|
+
emitTextDelta(getCallbacks, "Muting you for thirty seconds. [MUTE:30]");
|
|
1036
|
+
emitMessageComplete(getCallbacks);
|
|
1037
|
+
await waitFor(() =>
|
|
1038
|
+
frames.some((frame) => frame.type === "session_control"),
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
expect(
|
|
1042
|
+
frames.find((frame) => frame.type === "session_control"),
|
|
1043
|
+
).toMatchObject({ action: "mute", durationMs: 30_000 });
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
test("a control the client did not declare is never sent", async () => {
|
|
1047
|
+
const { frames, session, getCallbacks } = createControlsHarness(["mute"]);
|
|
1048
|
+
|
|
1049
|
+
await startReleasedTurn(session, getCallbacks);
|
|
1050
|
+
emitTextDelta(getCallbacks, "Bye. [END_CALL]");
|
|
1051
|
+
emitMessageComplete(getCallbacks);
|
|
1052
|
+
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
1053
|
+
await flushAsyncCallbacks();
|
|
1054
|
+
|
|
1055
|
+
expect(frames.some((frame) => frame.type === "session_control")).toBe(
|
|
1056
|
+
false,
|
|
1057
|
+
);
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
test("a marker mid-reply controls nothing", async () => {
|
|
1061
|
+
const { frames, session, getCallbacks } = createControlsHarness(["end"]);
|
|
1062
|
+
|
|
1063
|
+
await startReleasedTurn(session, getCallbacks);
|
|
1064
|
+
emitTextDelta(getCallbacks, "Say [END_CALL] and I would hang up, but no.");
|
|
1065
|
+
emitMessageComplete(getCallbacks);
|
|
1066
|
+
await waitFor(() => frames.some((frame) => frame.type === "tts_done"));
|
|
1067
|
+
await flushAsyncCallbacks();
|
|
1068
|
+
|
|
1069
|
+
expect(frames.some((frame) => frame.type === "session_control")).toBe(
|
|
1070
|
+
false,
|
|
1071
|
+
);
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
// Talking over "okay, bye" means they are not leaving.
|
|
1075
|
+
test("an interrupted goodbye does not end the call", async () => {
|
|
1076
|
+
const { frames, session, getCallbacks } = createControlsHarness(["end"]);
|
|
1077
|
+
|
|
1078
|
+
await startReleasedTurn(session, getCallbacks);
|
|
1079
|
+
emitTextDelta(getCallbacks, "Okay, talk soon. [END_CALL]");
|
|
1080
|
+
await flushAsyncCallbacks();
|
|
1081
|
+
await session.handleClientFrame({ type: "interrupt" });
|
|
1082
|
+
emitMessageComplete(getCallbacks);
|
|
1083
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1084
|
+
|
|
1085
|
+
expect(frames.some((frame) => frame.type === "session_control")).toBe(
|
|
1086
|
+
false,
|
|
1087
|
+
);
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
test("the prompt teaches only the declared controls", async () => {
|
|
1091
|
+
const { session, getCallbacks, startVoiceTurn } = createControlsHarness([
|
|
1092
|
+
"mute",
|
|
1093
|
+
]);
|
|
1094
|
+
|
|
1095
|
+
await startReleasedTurn(session, getCallbacks);
|
|
1096
|
+
const prompt =
|
|
1097
|
+
(startVoiceTurn as ReturnType<typeof mock>).mock.calls[0]?.[0]
|
|
1098
|
+
?.voiceControlPrompt ?? "";
|
|
1099
|
+
|
|
1100
|
+
expect(prompt).toContain("[MUTE]");
|
|
1101
|
+
expect(prompt).not.toContain("[END_CALL]");
|
|
1102
|
+
});
|
|
1103
|
+
});
|
|
@@ -253,14 +253,15 @@ describe("LiveVoiceSession archive and metrics events", () => {
|
|
|
253
253
|
userMessageInterface: "macos",
|
|
254
254
|
assistantMessageInterface: "macos",
|
|
255
255
|
// Pins the full production control prompt for the FRONT-DOOR leg (the
|
|
256
|
-
// first leg of every routed turn): the speech-first rule
|
|
257
|
-
// else. This leg is toolless, so both the
|
|
258
|
-
// teachings are deliberately absent. It
|
|
259
|
-
// connection to run, and its capability
|
|
260
|
-
// needing a tool to the escalated leg
|
|
261
|
-
// live-voice-triage-escalate.test.ts).
|
|
256
|
+
// first leg of every routed turn): the speech-first rule and the session
|
|
257
|
+
// controls, and nothing else. This leg is toolless, so both the
|
|
258
|
+
// screen-reveal and setup-flow teachings are deliberately absent. It
|
|
259
|
+
// has nothing to show and no connection to run, and its capability
|
|
260
|
+
// digest already routes anything needing a tool to the escalated leg
|
|
261
|
+
// (see live-voice-triage-escalate.test.ts). The start frame declares no
|
|
262
|
+
// client controls, so only the session's own update cadence is taught.
|
|
262
263
|
voiceControlPrompt:
|
|
263
|
-
|
|
264
|
+
'You are speaking in a local live voice session. Keep each reply to one to three spoken sentences. The caller is listening, not reading: lead with the answer, say it in the fewest words that are still complete, and stop. No preamble, no recap of the question, no closing offer of more. Your text is sent directly to a text-to-speech engine. Never use markdown formatting (asterisks, headers, backticks, links), bulleted or numbered lists, or emojis. Write plain conversational text only; if something is naturally a list, say it as one sentence. Speech is the main channel: say the answer, and do not narrate a surface instead of answering. You can also put something on screen when it genuinely helps (a form, a list to pick from, a progress card for long work, or a card with an image or a link instead of reading an address out); the call overlay minimizes by itself once you finish speaking, so the user sees it without doing anything. Never tell the user you cannot show them something. Reply in the language the caller is speaking; if they switch languages, switch with them. The user can also control this call by asking you. Only when they clearly ask:\n- To hear fewer spoken progress updates while you work (for example "don\'t give me updates so often"), confirm that you will only check in now and then and will tell them when it is done, then end your reply with [UPDATES:FEWER]. If they later want regular updates back, confirm and end with [UPDATES:NORMAL].\nThe marker must be the very last thing in your reply. It is never spoken and does nothing anywhere else.',
|
|
264
265
|
});
|
|
265
266
|
callbacks?.assistant_text_delta?.(makeTextDelta("Hello there."));
|
|
266
267
|
callbacks?.message_complete?.(makeMessageComplete());
|
|
@@ -329,7 +329,6 @@ describe("LiveVoiceMetricsCollector", () => {
|
|
|
329
329
|
endpointHoldCount: 0,
|
|
330
330
|
endpointDecisionMaxLatencyMs: 95,
|
|
331
331
|
});
|
|
332
|
-
expect(completed).not.toHaveProperty("ackSpoken");
|
|
333
332
|
});
|
|
334
333
|
|
|
335
334
|
test("decisions without a source are attributed to the front door", () => {
|
|
@@ -435,7 +434,6 @@ describe("LiveVoiceMetricsCollector", () => {
|
|
|
435
434
|
expect(completed).not.toHaveProperty("endpointHoldCount");
|
|
436
435
|
expect(completed).not.toHaveProperty("endpointDecisionMaxLatencyMs");
|
|
437
436
|
expect(completed).not.toHaveProperty("endpointDecisionSource");
|
|
438
|
-
expect(completed).not.toHaveProperty("ackSpoken");
|
|
439
437
|
expect(completed).not.toHaveProperty("progressUpdatesSpoken");
|
|
440
438
|
|
|
441
439
|
const aggregateFields = getLiveVoiceMetricsAggregateFields(
|
|
@@ -446,7 +444,6 @@ describe("LiveVoiceMetricsCollector", () => {
|
|
|
446
444
|
expect(aggregateFields).not.toHaveProperty("endpointHoldCount");
|
|
447
445
|
expect(aggregateFields).not.toHaveProperty("endpointDecisionMaxLatencyMs");
|
|
448
446
|
expect(aggregateFields).not.toHaveProperty("endpointDecisionSource");
|
|
449
|
-
expect(aggregateFields).not.toHaveProperty("ackSpoken");
|
|
450
447
|
expect(aggregateFields).not.toHaveProperty("progressUpdatesSpoken");
|
|
451
448
|
});
|
|
452
449
|
|