@vellumai/assistant 0.12.1 → 0.12.2-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/ARCHITECTURE.md +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/openapi.yaml +327 -52
- 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-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- 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 +212 -3
- package/src/__tests__/credential-security-invariants.test.ts +4 -2
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- 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 +539 -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 +113 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -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__/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 +19 -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__/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__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -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__/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/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/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/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 +58 -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 +37 -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 +53 -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 +59 -46
- package/src/calls/voice-triage-escalate.ts +175 -56
- 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/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 +20 -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 +24 -12
- package/src/config/loader.ts +1 -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 +8 -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 +2 -1
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +15 -9
- package/src/daemon/conversation-tool-setup.ts +37 -43
- 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 +186 -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-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -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__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +213 -693
- package/src/live-voice/protocol.ts +0 -2
- 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 +54 -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 +73 -0
- package/src/oauth/connection-resolver.ts +95 -2
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +204 -25
- 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-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -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-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +73 -0
- 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 +19 -10
- 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 +36 -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/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-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
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { NON_LATIN_SENTENCE_ENDING_PUNCTUATION } from "../tts/speakable-segments.js";
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
fixedPhraseLanguage,
|
|
32
|
+
localizedOrDefault,
|
|
33
|
+
} from "../util/language-subtag.js";
|
|
31
34
|
import {
|
|
32
35
|
ESCALATE_VERDICT_TOKEN,
|
|
33
36
|
HOLD_VERDICT_TOKEN,
|
|
@@ -103,6 +106,36 @@ export function fallbackEscalationBridgeFor(language?: string): string {
|
|
|
103
106
|
*/
|
|
104
107
|
export const MIN_SPOKEN_BRIDGE_CHARS = 3;
|
|
105
108
|
|
|
109
|
+
/**
|
|
110
|
+
* The phrase spoken across an escalation hand-off, for both voice drivers:
|
|
111
|
+
* the front-door leg's own capped bridge when it is a real bridge, else the
|
|
112
|
+
* canned fallback in the caller's language. `usesFallback` marks the canned
|
|
113
|
+
* phrase as audio-only: the model never produced it, so no transcript row
|
|
114
|
+
* carries it (the bridge's hygiene pass deletes the leg's row), and the
|
|
115
|
+
* driver keeps it out of the turn's recorded text. `language` is the TTS
|
|
116
|
+
* hint the phrase must carry: "en" when the canned table has no entry for
|
|
117
|
+
* the caller's language (the phrase is English text then), undefined when
|
|
118
|
+
* the phrase rides the turn's own language.
|
|
119
|
+
*/
|
|
120
|
+
export function resolveSpokenEscalationBridge(
|
|
121
|
+
cappedBridge: string,
|
|
122
|
+
language?: string,
|
|
123
|
+
): { spokenBridge: string; usesFallback: boolean; language?: string } {
|
|
124
|
+
const usesFallback = cappedBridge.length < MIN_SPOKEN_BRIDGE_CHARS;
|
|
125
|
+
if (!usesFallback) {
|
|
126
|
+
return { spokenBridge: cappedBridge, usesFallback };
|
|
127
|
+
}
|
|
128
|
+
const fixedLanguage = fixedPhraseLanguage(
|
|
129
|
+
FALLBACK_ESCALATION_BRIDGE_BY_LANGUAGE,
|
|
130
|
+
language,
|
|
131
|
+
);
|
|
132
|
+
return {
|
|
133
|
+
spokenBridge: fallbackEscalationBridgeFor(language),
|
|
134
|
+
usesFallback,
|
|
135
|
+
...(fixedLanguage !== undefined ? { language: fixedLanguage } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
106
139
|
/**
|
|
107
140
|
* Hard cap on the spoken escalation bridge. The bridge is supposed to be a
|
|
108
141
|
* single short sentence; the cap bounds the hand-off delay (and the audio)
|
|
@@ -159,14 +192,20 @@ export function isEscalationBridgeComplete(rawBridge: string): boolean {
|
|
|
159
192
|
* output leads with {@link ESCALATE_VERDICT_TOKEN} (a stray token later in
|
|
160
193
|
* an answer is not an escalation under the verdict-first protocol), else
|
|
161
194
|
* the capped bridge that followed the token. Used by transcript hygiene to
|
|
162
|
-
* reconstruct what the caller heard from a persisted row
|
|
195
|
+
* reconstruct what the caller heard from a persisted row, by running the
|
|
196
|
+
* same verdict machine the live stream ran through.
|
|
163
197
|
*/
|
|
164
198
|
export function spokenBridgeText(frontDoorText: string): string {
|
|
165
|
-
const
|
|
166
|
-
|
|
199
|
+
const machine = createFrontDoorVerdictMachine(false);
|
|
200
|
+
const step = machine.push(frontDoorText);
|
|
201
|
+
if (step.kind !== "escalate") {
|
|
167
202
|
return "";
|
|
168
203
|
}
|
|
169
|
-
|
|
204
|
+
if (step.bridge !== null) {
|
|
205
|
+
return step.bridge;
|
|
206
|
+
}
|
|
207
|
+
const finished = machine.finish();
|
|
208
|
+
return finished.kind === "bridge" ? finished.bridge : "";
|
|
170
209
|
}
|
|
171
210
|
|
|
172
211
|
/**
|
|
@@ -224,107 +263,187 @@ export function classifyFrontDoorLeading(
|
|
|
224
263
|
}
|
|
225
264
|
|
|
226
265
|
/**
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
266
|
+
* One step of a front-door leg's stream under the verdict-first protocol,
|
|
267
|
+
* as returned by {@link FrontDoorVerdictMachine.push}.
|
|
268
|
+
*/
|
|
269
|
+
export type FrontDoorStep =
|
|
270
|
+
/** The leading tokens could still become a verdict token: keep buffering. */
|
|
271
|
+
| { readonly kind: "pending" }
|
|
272
|
+
/** Hold verdict: the leg is discarded and listening continues. */
|
|
273
|
+
| { readonly kind: "hold" }
|
|
274
|
+
/**
|
|
275
|
+
* The leg's output is the answer. `text` is the raw text this delta
|
|
276
|
+
* released; on the transition it includes the leading text held while the
|
|
277
|
+
* verdict was pending.
|
|
278
|
+
*/
|
|
279
|
+
| { readonly kind: "answer"; readonly text: string }
|
|
280
|
+
/**
|
|
281
|
+
* Escalate verdict, decided by this delta. `bridge` is the capped holding
|
|
282
|
+
* phrase when this delta already completed it, else null while the phrase
|
|
283
|
+
* keeps streaming.
|
|
284
|
+
*/
|
|
285
|
+
| { readonly kind: "escalate"; readonly bridge: string | null }
|
|
286
|
+
/** Post-verdict text still buffering toward the capped bridge. */
|
|
287
|
+
| { readonly kind: "bridging" }
|
|
288
|
+
/** The capped bridge is complete: hand off to the escalated leg. */
|
|
289
|
+
| { readonly kind: "bridge"; readonly bridge: string }
|
|
290
|
+
/** The leg already held, handed off, or finished; nothing more happens. */
|
|
291
|
+
| { readonly kind: "done" };
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* The verdict-first state machine over a front-door leg's delta stream. One
|
|
295
|
+
* implementation drives every consumer of that stream: the live-voice
|
|
296
|
+
* session (which acts on the steps), the conversation-hub stream gate
|
|
297
|
+
* (which releases only the text the caller hears), and transcript hygiene
|
|
298
|
+
* (which replays a persisted row through it).
|
|
232
299
|
*
|
|
233
|
-
* `push`
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* simply never calls `finish`.
|
|
300
|
+
* `push` feeds one delta and reports what it decided. `finish` is called
|
|
301
|
+
* when the leg completes normally: a bridge that stopped short of a
|
|
302
|
+
* sentence terminator hands off with what arrived. A leg that is cancelled
|
|
303
|
+
* instead never calls `finish`.
|
|
238
304
|
*/
|
|
239
|
-
export interface
|
|
240
|
-
push(deltaText: string):
|
|
241
|
-
finish():
|
|
305
|
+
export interface FrontDoorVerdictMachine {
|
|
306
|
+
push(deltaText: string): FrontDoorStep;
|
|
307
|
+
finish(): FrontDoorStep;
|
|
242
308
|
}
|
|
243
309
|
|
|
310
|
+
const PENDING_STEP: FrontDoorStep = { kind: "pending" };
|
|
311
|
+
const HOLD_STEP: FrontDoorStep = { kind: "hold" };
|
|
312
|
+
const BRIDGING_STEP: FrontDoorStep = { kind: "bridging" };
|
|
313
|
+
const DONE_STEP: FrontDoorStep = { kind: "done" };
|
|
314
|
+
|
|
244
315
|
/**
|
|
245
|
-
* Build a {@link
|
|
316
|
+
* Build a {@link FrontDoorVerdictMachine}. `holdEnabled` mirrors
|
|
246
317
|
* {@link classifyFrontDoorLeading}: true only for speculative (unified
|
|
247
318
|
* front-door) legs, whose decision rule is the only one that teaches the
|
|
248
319
|
* hold token.
|
|
249
320
|
*
|
|
250
|
-
* The
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* - `escalate`: the only spoken text is the capped holding phrase, released
|
|
255
|
-
* in one piece once the bridge is complete (exactly what
|
|
256
|
-
* {@link capEscalationBridge} yields, so the released text, the audio, and
|
|
257
|
-
* the persisted row agree). The verdict token and anything streamed past
|
|
258
|
-
* the cap are dropped. A bridge shorter than
|
|
259
|
-
* {@link MIN_SPOKEN_BRIDGE_CHARS} releases nothing at all: the session
|
|
260
|
-
* substitutes an audio-only canned fallback for it, so there is no
|
|
261
|
-
* displayed text for the gate to agree with.
|
|
262
|
-
* - `answer`: the leg's output IS the reply, so every delta passes through,
|
|
263
|
-
* including the leading text held back while the verdict was pending.
|
|
321
|
+
* The bridge handed off is exactly what {@link capEscalationBridge} yields,
|
|
322
|
+
* so the audio, the released text, the persisted row, and the phrase quoted
|
|
323
|
+
* to the escalated leg all agree. The verdict token and anything streamed
|
|
324
|
+
* past the cap are never released.
|
|
264
325
|
*/
|
|
265
|
-
export function
|
|
326
|
+
export function createFrontDoorVerdictMachine(
|
|
266
327
|
holdEnabled: boolean,
|
|
267
|
-
):
|
|
328
|
+
): FrontDoorVerdictMachine {
|
|
268
329
|
let raw = "";
|
|
269
330
|
let stage: "deciding" | "answer" | "bridging" | "done" = "deciding";
|
|
270
331
|
let bridgeRaw = "";
|
|
271
332
|
let releasedChars = 0;
|
|
272
333
|
|
|
273
|
-
const
|
|
334
|
+
const completeBridge = (): Extract<FrontDoorStep, { kind: "bridge" }> => {
|
|
274
335
|
stage = "done";
|
|
275
|
-
|
|
276
|
-
// Below the spoken threshold the session throws the model's bridge away
|
|
277
|
-
// and plays a canned fallback that is audio-only, deleting the row rather
|
|
278
|
-
// than persisting a phrase the model never really produced (see
|
|
279
|
-
// `usesFallbackBridge` in `live-voice-session.ts`). Releasing the capped
|
|
280
|
-
// text here would put words on a subscriber's screen that the caller never
|
|
281
|
-
// heard, which is the same spoken/displayed divergence this gate exists to
|
|
282
|
-
// prevent.
|
|
283
|
-
return capped.length < MIN_SPOKEN_BRIDGE_CHARS ? "" : capped;
|
|
336
|
+
return { kind: "bridge", bridge: capEscalationBridge(bridgeRaw) };
|
|
284
337
|
};
|
|
285
338
|
|
|
286
339
|
return {
|
|
287
|
-
push(deltaText: string):
|
|
340
|
+
push(deltaText: string): FrontDoorStep {
|
|
288
341
|
raw += deltaText;
|
|
289
342
|
if (stage === "done") {
|
|
290
|
-
return
|
|
343
|
+
return DONE_STEP;
|
|
291
344
|
}
|
|
292
345
|
if (stage === "bridging") {
|
|
293
346
|
bridgeRaw += deltaText;
|
|
294
|
-
return isEscalationBridgeComplete(bridgeRaw)
|
|
347
|
+
return isEscalationBridgeComplete(bridgeRaw)
|
|
348
|
+
? completeBridge()
|
|
349
|
+
: BRIDGING_STEP;
|
|
295
350
|
}
|
|
296
351
|
if (stage === "deciding") {
|
|
297
352
|
const verdict = classifyFrontDoorLeading(raw.trimStart(), holdEnabled);
|
|
298
353
|
if (verdict === "pending") {
|
|
299
|
-
return
|
|
354
|
+
return PENDING_STEP;
|
|
300
355
|
}
|
|
301
356
|
if (verdict === "hold") {
|
|
302
357
|
stage = "done";
|
|
303
|
-
return
|
|
358
|
+
return HOLD_STEP;
|
|
304
359
|
}
|
|
305
360
|
if (verdict === "escalate") {
|
|
306
361
|
stage = "bridging";
|
|
307
362
|
bridgeRaw = raw.trimStart().slice(ESCALATE_VERDICT_TOKEN.length);
|
|
308
|
-
return
|
|
363
|
+
return {
|
|
364
|
+
kind: "escalate",
|
|
365
|
+
bridge: isEscalationBridgeComplete(bridgeRaw)
|
|
366
|
+
? completeBridge().bridge
|
|
367
|
+
: null,
|
|
368
|
+
};
|
|
309
369
|
}
|
|
310
370
|
stage = "answer";
|
|
311
371
|
}
|
|
312
372
|
// Answer stage: release everything not yet released, which on the
|
|
313
373
|
// transition includes the leading text the pending verdict held.
|
|
314
|
-
const
|
|
374
|
+
const text = raw.slice(releasedChars);
|
|
315
375
|
releasedChars = raw.length;
|
|
316
|
-
return
|
|
376
|
+
return { kind: "answer", text };
|
|
317
377
|
},
|
|
318
|
-
finish():
|
|
378
|
+
finish(): FrontDoorStep {
|
|
319
379
|
if (stage === "bridging") {
|
|
320
|
-
return
|
|
380
|
+
return completeBridge();
|
|
321
381
|
}
|
|
322
382
|
stage = "done";
|
|
323
|
-
return
|
|
383
|
+
return DONE_STEP;
|
|
324
384
|
},
|
|
325
385
|
};
|
|
326
386
|
}
|
|
327
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Verdict-first gate over a front-door leg's delta stream: given the leg's
|
|
390
|
+
* raw deltas in order, it releases only the text the caller actually hears.
|
|
391
|
+
* A front-door leg's raw stream is a control plane, not assistant speech,
|
|
392
|
+
* until its leading tokens classify, so anything downstream of the model
|
|
393
|
+
* that shows text to a person reads the stream through this gate.
|
|
394
|
+
*
|
|
395
|
+
* `push` returns the text released by that delta (empty while the gate is
|
|
396
|
+
* holding). `finish` is called when the leg completes normally and releases
|
|
397
|
+
* a bridge that stopped short of a sentence terminator. A leg that is
|
|
398
|
+
* cancelled instead spoke nothing past what `push` already released, so it
|
|
399
|
+
* simply never calls `finish`.
|
|
400
|
+
*/
|
|
401
|
+
export interface FrontDoorStreamGate {
|
|
402
|
+
push(deltaText: string): string;
|
|
403
|
+
finish(): string;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Build a {@link FrontDoorStreamGate} over a {@link FrontDoorVerdictMachine}.
|
|
408
|
+
*
|
|
409
|
+
* The three verdicts release differently, matching what the caller hears:
|
|
410
|
+
*
|
|
411
|
+
* - `hold`: the leg is discarded and its row deleted, so nothing is ever
|
|
412
|
+
* released.
|
|
413
|
+
* - `escalate`: the only spoken text is the capped holding phrase, released
|
|
414
|
+
* in one piece once the bridge is complete. A bridge shorter than
|
|
415
|
+
* {@link MIN_SPOKEN_BRIDGE_CHARS} releases nothing at all: the session
|
|
416
|
+
* substitutes an audio-only canned fallback for it (see
|
|
417
|
+
* `usesFallbackBridge` in `live-voice-session.ts`) and deletes the row
|
|
418
|
+
* rather than persisting a phrase the model never really produced, so
|
|
419
|
+
* there is no displayed text for the gate to agree with.
|
|
420
|
+
* - `answer`: the leg's output IS the reply, so every delta passes through,
|
|
421
|
+
* including the leading text held back while the verdict was pending.
|
|
422
|
+
*/
|
|
423
|
+
export function createFrontDoorStreamGate(
|
|
424
|
+
holdEnabled: boolean,
|
|
425
|
+
): FrontDoorStreamGate {
|
|
426
|
+
const machine = createFrontDoorVerdictMachine(holdEnabled);
|
|
427
|
+
const releasable = (bridge: string): string =>
|
|
428
|
+
bridge.length < MIN_SPOKEN_BRIDGE_CHARS ? "" : bridge;
|
|
429
|
+
const release = (step: FrontDoorStep): string => {
|
|
430
|
+
switch (step.kind) {
|
|
431
|
+
case "answer":
|
|
432
|
+
return step.text;
|
|
433
|
+
case "escalate":
|
|
434
|
+
return step.bridge === null ? "" : releasable(step.bridge);
|
|
435
|
+
case "bridge":
|
|
436
|
+
return releasable(step.bridge);
|
|
437
|
+
default:
|
|
438
|
+
return "";
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
return {
|
|
442
|
+
push: (deltaText) => release(machine.push(deltaText)),
|
|
443
|
+
finish: () => release(machine.finish()),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
328
447
|
/**
|
|
329
448
|
* The escalated leg runs as its own voice turn. Rather than re-persist the
|
|
330
449
|
* caller's utterance (it is already in history from the front-door leg), the
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `assistant apps inspect` and `assistant apps refresh`.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
import { createApp, getAppDirPath } from "../../../apps/app-store.js";
|
|
11
|
+
import { writeAppSourceFingerprint } from "../../../apps/source-fingerprint.js";
|
|
12
|
+
import { getWorkspacePluginsDir } from "../../../util/platform.js";
|
|
13
|
+
import { runCliCommand } from "./cli-test-harness.js";
|
|
14
|
+
|
|
15
|
+
let lastIpcCall: {
|
|
16
|
+
method: string;
|
|
17
|
+
params?: Record<string, unknown>;
|
|
18
|
+
} | null = null;
|
|
19
|
+
|
|
20
|
+
let mockIpcResult: {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
result?: unknown;
|
|
23
|
+
error?: string;
|
|
24
|
+
statusCode?: number;
|
|
25
|
+
} = { ok: true, result: { ok: true, compiled: true } };
|
|
26
|
+
|
|
27
|
+
mock.module("../../../ipc/cli-client.js", () => ({
|
|
28
|
+
cliIpcCall: async (method: string, params?: Record<string, unknown>) => {
|
|
29
|
+
lastIpcCall = { method, params };
|
|
30
|
+
return mockIpcResult;
|
|
31
|
+
},
|
|
32
|
+
exitCodeFromIpcResult: (r: { statusCode?: number }) => {
|
|
33
|
+
if (r.statusCode === undefined) {
|
|
34
|
+
return 10;
|
|
35
|
+
}
|
|
36
|
+
if (r.statusCode >= 500) {
|
|
37
|
+
return 3;
|
|
38
|
+
}
|
|
39
|
+
if (r.statusCode >= 400) {
|
|
40
|
+
return 2;
|
|
41
|
+
}
|
|
42
|
+
return 1;
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
const { registerAppsCommand } = await import("../apps.js");
|
|
47
|
+
|
|
48
|
+
let workspaceDir: string;
|
|
49
|
+
let previousWorkspaceDir: string | undefined;
|
|
50
|
+
|
|
51
|
+
function freshWorkspace(): string {
|
|
52
|
+
return join(
|
|
53
|
+
tmpdir(),
|
|
54
|
+
`vellum-apps-cli-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function uniqueAppName(prefix: string): string {
|
|
59
|
+
return `${prefix} ${Math.random().toString(36).slice(2, 8)}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
previousWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
64
|
+
workspaceDir = freshWorkspace();
|
|
65
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
66
|
+
lastIpcCall = null;
|
|
67
|
+
mockIpcResult = {
|
|
68
|
+
ok: true,
|
|
69
|
+
result: {
|
|
70
|
+
ok: true,
|
|
71
|
+
appId: "app-1",
|
|
72
|
+
name: "Budget",
|
|
73
|
+
compiled: true,
|
|
74
|
+
compile_duration_ms: 12,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
afterEach(() => {
|
|
80
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
81
|
+
if (previousWorkspaceDir === undefined) {
|
|
82
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
83
|
+
} else {
|
|
84
|
+
process.env.VELLUM_WORKSPACE_DIR = previousWorkspaceDir;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
function parseJson(stdout: string): Record<string, unknown> {
|
|
89
|
+
return JSON.parse(stdout.trim()) as Record<string, unknown>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
describe("assistant apps inspect", () => {
|
|
93
|
+
test("reports never_compiled for a new workspace app", async () => {
|
|
94
|
+
const name = uniqueAppName("Inspect Fresh");
|
|
95
|
+
const created = createApp({
|
|
96
|
+
name,
|
|
97
|
+
schemaJson: "{}",
|
|
98
|
+
htmlDefinition: "<h1>Budget</h1>",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const result = await runCliCommand(registerAppsCommand, [
|
|
102
|
+
"apps",
|
|
103
|
+
"inspect",
|
|
104
|
+
created.id,
|
|
105
|
+
"--json",
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
expect(result.exitCode).toBe(0);
|
|
109
|
+
const body = parseJson(result.stdout);
|
|
110
|
+
expect(body.ok).toBe(true);
|
|
111
|
+
const compile = body.compile as { status: string };
|
|
112
|
+
expect(compile.status).toBe("never_compiled");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("reports stale after source changes since the last fingerprint", async () => {
|
|
116
|
+
const name = uniqueAppName("Inspect Stale");
|
|
117
|
+
const created = createApp({
|
|
118
|
+
name,
|
|
119
|
+
schemaJson: "{}",
|
|
120
|
+
htmlDefinition: "<h1>Budget</h1>",
|
|
121
|
+
});
|
|
122
|
+
const appDir = getAppDirPath(created.id);
|
|
123
|
+
expect(appDir.length).toBeGreaterThan(0);
|
|
124
|
+
mkdirSync(join(appDir, "src"), { recursive: true });
|
|
125
|
+
writeFileSync(join(appDir, "src", "main.tsx"), "export const n = 1;\n");
|
|
126
|
+
mkdirSync(join(appDir, "dist"), { recursive: true });
|
|
127
|
+
writeFileSync(join(appDir, "dist", "index.html"), "<html></html>");
|
|
128
|
+
writeAppSourceFingerprint(appDir, join(appDir, "dist"));
|
|
129
|
+
writeFileSync(join(appDir, "src", "main.tsx"), "export const n = 2;\n");
|
|
130
|
+
|
|
131
|
+
const result = await runCliCommand(registerAppsCommand, [
|
|
132
|
+
"apps",
|
|
133
|
+
"inspect",
|
|
134
|
+
created.id,
|
|
135
|
+
"--json",
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
expect(result.exitCode).toBe(0);
|
|
139
|
+
const body = parseJson(result.stdout);
|
|
140
|
+
const app = body.app as { id: string; source: string };
|
|
141
|
+
expect(app.id).toBe(created.id);
|
|
142
|
+
expect(app.source).toBe(appDir);
|
|
143
|
+
const compile = body.compile as {
|
|
144
|
+
status: string;
|
|
145
|
+
modified: string[];
|
|
146
|
+
};
|
|
147
|
+
expect(compile.status).toBe("stale");
|
|
148
|
+
expect(compile.modified).toEqual(["src/main.tsx"]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("exits 1 for an unknown app", async () => {
|
|
152
|
+
const result = await runCliCommand(registerAppsCommand, [
|
|
153
|
+
"apps",
|
|
154
|
+
"inspect",
|
|
155
|
+
"missing",
|
|
156
|
+
"--json",
|
|
157
|
+
]);
|
|
158
|
+
expect(result.exitCode).toBe(1);
|
|
159
|
+
const body = parseJson(result.stdout);
|
|
160
|
+
expect(body.ok).toBe(false);
|
|
161
|
+
expect(String(body.error)).toContain("assistant apps list");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("assistant apps refresh", () => {
|
|
166
|
+
test("sends apps_refresh IPC for a workspace app", async () => {
|
|
167
|
+
const name = uniqueAppName("Refresh");
|
|
168
|
+
const created = createApp({
|
|
169
|
+
name,
|
|
170
|
+
schemaJson: "{}",
|
|
171
|
+
htmlDefinition: "<h1>Budget</h1>",
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = await runCliCommand(registerAppsCommand, [
|
|
175
|
+
"apps",
|
|
176
|
+
"refresh",
|
|
177
|
+
created.id,
|
|
178
|
+
"--json",
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
expect(result.exitCode).toBe(0);
|
|
182
|
+
expect(lastIpcCall).toEqual({
|
|
183
|
+
method: "apps_refresh",
|
|
184
|
+
params: { pathParams: { id: created.id } },
|
|
185
|
+
});
|
|
186
|
+
const body = parseJson(result.stdout);
|
|
187
|
+
expect(body.compiled).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("sends apps_refresh IPC for a plugin app", async () => {
|
|
191
|
+
const pluginName = `charts-${Math.random().toString(36).slice(2, 8)}`;
|
|
192
|
+
const pluginDir = join(getWorkspacePluginsDir(), pluginName);
|
|
193
|
+
mkdirSync(pluginDir, { recursive: true });
|
|
194
|
+
writeFileSync(
|
|
195
|
+
join(pluginDir, "package.json"),
|
|
196
|
+
JSON.stringify({ name: pluginName, version: "1.0.0" }),
|
|
197
|
+
);
|
|
198
|
+
mkdirSync(join(pluginDir, "apps", "viewer"), { recursive: true });
|
|
199
|
+
writeFileSync(join(pluginDir, "apps", "viewer", "index.html"), "<h1></h1>");
|
|
200
|
+
|
|
201
|
+
const result = await runCliCommand(registerAppsCommand, [
|
|
202
|
+
"apps",
|
|
203
|
+
"refresh",
|
|
204
|
+
"viewer",
|
|
205
|
+
"--json",
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
expect(result.exitCode).toBe(0);
|
|
209
|
+
expect(lastIpcCall).toEqual({
|
|
210
|
+
method: "apps_refresh",
|
|
211
|
+
params: { pathParams: { id: `plugins~${pluginName}~viewer` } },
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("exits 1 when compile fails", async () => {
|
|
216
|
+
const name = uniqueAppName("Refresh Fail");
|
|
217
|
+
const created = createApp({
|
|
218
|
+
name,
|
|
219
|
+
schemaJson: "{}",
|
|
220
|
+
htmlDefinition: "<h1>Budget</h1>",
|
|
221
|
+
});
|
|
222
|
+
mockIpcResult = {
|
|
223
|
+
ok: true,
|
|
224
|
+
result: {
|
|
225
|
+
ok: true,
|
|
226
|
+
appId: created.id,
|
|
227
|
+
name,
|
|
228
|
+
compiled: false,
|
|
229
|
+
compile_duration_ms: 8,
|
|
230
|
+
compile_errors: [{ text: "Could not resolve foo" }],
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const result = await runCliCommand(registerAppsCommand, [
|
|
235
|
+
"apps",
|
|
236
|
+
"refresh",
|
|
237
|
+
created.id,
|
|
238
|
+
"--json",
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
expect(result.exitCode).toBe(1);
|
|
242
|
+
const body = parseJson(result.stdout);
|
|
243
|
+
expect(body.compiled).toBe(false);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `assistant bash`: local sanitized spawn, no IPC round-trip.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
6
|
+
|
|
7
|
+
import { runCliCommand } from "./cli-test-harness.js";
|
|
8
|
+
|
|
9
|
+
const ipcCalls: Array<{ method: string }> = [];
|
|
10
|
+
|
|
11
|
+
mock.module("../../../ipc/cli-client.js", () => ({
|
|
12
|
+
cliIpcCall: async (method: string) => {
|
|
13
|
+
ipcCalls.push({ method });
|
|
14
|
+
return { ok: false, error: "IPC should not be used" };
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
mock.module("../../logger.js", () => ({
|
|
19
|
+
log: {
|
|
20
|
+
info: () => {},
|
|
21
|
+
warn: () => {},
|
|
22
|
+
error: (message: string) => {
|
|
23
|
+
process.stderr.write(`${message}\n`);
|
|
24
|
+
},
|
|
25
|
+
debug: () => {},
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const { registerBashCommand } = await import("../bash.js");
|
|
30
|
+
|
|
31
|
+
describe("assistant bash", () => {
|
|
32
|
+
test("spawns locally and prints stdout without IPC", async () => {
|
|
33
|
+
ipcCalls.length = 0;
|
|
34
|
+
const result = await runCliCommand(registerBashCommand, [
|
|
35
|
+
"bash",
|
|
36
|
+
"echo hello",
|
|
37
|
+
]);
|
|
38
|
+
expect(ipcCalls).toEqual([]);
|
|
39
|
+
expect(result.stdout.trim()).toBe("hello");
|
|
40
|
+
expect(result.exitCode).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("rejects a non-positive timeout", async () => {
|
|
44
|
+
const result = await runCliCommand(registerBashCommand, [
|
|
45
|
+
"bash",
|
|
46
|
+
"echo hello",
|
|
47
|
+
"--timeout",
|
|
48
|
+
"0",
|
|
49
|
+
]);
|
|
50
|
+
expect(result.stderr).toContain("Invalid timeout value");
|
|
51
|
+
expect(result.exitCode).toBe(1);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { roadmapHelp } from "../roadmap.help.js";
|
|
4
|
+
|
|
5
|
+
const DISCORD_URL = "https://vellum.ai/community";
|
|
6
|
+
|
|
7
|
+
describe("roadmap help is feature-proposal-only", () => {
|
|
8
|
+
test("top-level help describes feature proposals and routes bugs to Discord", () => {
|
|
9
|
+
const description = roadmapHelp.description;
|
|
10
|
+
const helpText = roadmapHelp.helpText ?? "";
|
|
11
|
+
const combined = `${description}\n${helpText}`.toLowerCase();
|
|
12
|
+
|
|
13
|
+
expect(combined).toMatch(/feature proposals?/);
|
|
14
|
+
expect(description.split(DISCORD_URL)).toHaveLength(2);
|
|
15
|
+
expect(helpText).toContain(DISCORD_URL);
|
|
16
|
+
expect(combined).toContain("instruct the user");
|
|
17
|
+
expect(combined).not.toMatch(/support@|github\.com|share feedback/);
|
|
18
|
+
});
|
|
19
|
+
});
|