@vellumai/assistant 0.12.0 → 0.12.1-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 +5 -1
- package/ARCHITECTURE.md +11 -12
- package/docs/architecture/turn-actor.md +15 -8
- package/openapi.yaml +292 -2
- package/package.json +1 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- 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__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +17 -5
- 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 +2 -1
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- 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__/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-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__/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__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +13 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/script-proxy-certs.test.ts +35 -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__/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__/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 +94 -9
- 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/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/index.ts +6 -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/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__/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/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 +117 -0
- package/src/config/__tests__/platform-rehydration.test.ts +93 -0
- 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/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/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 +2 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +8 -12
- 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 +144 -0
- package/src/config/platform-rehydration.ts +75 -21
- 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/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- 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-process.ts +35 -64
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +10 -8
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-file-proxy.ts +8 -10
- 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/providers-setup.ts +1 -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 +119 -0
- package/src/desktop/desktop-chrome-policy.test.ts +71 -0
- package/src/desktop/desktop-chrome-policy.ts +42 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +306 -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 +627 -0
- package/src/desktop/desktop-session-manager.ts +814 -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-theme.ts +170 -0
- 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-vad.test.ts +2 -2
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- 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__/home-feed-side-effect.test.ts +153 -3
- 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/home-feed-side-effect.ts +55 -7
- 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-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- 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 +2 -0
- 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-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/graph/capability-seed.ts +4 -6
- 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/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-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 +6 -1
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +89 -0
- 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/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/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- 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 +192 -78
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/runtime/AGENTS.md +2 -0
- 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__/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/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- 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__/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/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 +55 -3
- package/src/runtime/routes/index.ts +4 -0
- 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 +8 -21
- package/src/runtime/routes/platform-routes.ts +18 -15
- 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/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/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/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/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +4 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- 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/policy-context.ts +6 -7
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
- package/src/tools/terminal/safe-env.ts +23 -3
- package/src/tools/watch/watch-retro-report.ts +0 -8
- 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/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +5 -54
- 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/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -28,6 +28,9 @@ describe("model intents", () => {
|
|
|
28
28
|
expect(resolveModelIntent("openai", "latency-optimized")).toBe(
|
|
29
29
|
"gpt-5.6-luna",
|
|
30
30
|
);
|
|
31
|
+
expect(resolveModelIntent("gemini", "cost-optimized")).toBe(
|
|
32
|
+
"gemini-3.1-flash-lite",
|
|
33
|
+
);
|
|
31
34
|
expect(resolveModelIntent("gemini", "latency-optimized")).toBe(
|
|
32
35
|
"gemini-3.1-flash-lite",
|
|
33
36
|
);
|
|
@@ -429,7 +429,7 @@ describe("Outlook messaging provider", () => {
|
|
|
429
429
|
describe("sendMessage", () => {
|
|
430
430
|
test("sends a new message with correct recipient, subject, and body", async () => {
|
|
431
431
|
const conn = createMockConnection();
|
|
432
|
-
const result = await outlookMessagingProvider.sendMessage(
|
|
432
|
+
const result = await outlookMessagingProvider.sendMessage!(
|
|
433
433
|
conn,
|
|
434
434
|
"recipient@example.com",
|
|
435
435
|
"Hello!",
|
|
@@ -452,7 +452,7 @@ describe("Outlook messaging provider", () => {
|
|
|
452
452
|
|
|
453
453
|
test("uses empty string for subject when not provided", async () => {
|
|
454
454
|
const conn = createMockConnection();
|
|
455
|
-
await outlookMessagingProvider.sendMessage(
|
|
455
|
+
await outlookMessagingProvider.sendMessage!(
|
|
456
456
|
conn,
|
|
457
457
|
"recipient@example.com",
|
|
458
458
|
"Hello!",
|
|
@@ -471,7 +471,7 @@ describe("Outlook messaging provider", () => {
|
|
|
471
471
|
|
|
472
472
|
test("calls replyToMessage when inReplyTo is provided", async () => {
|
|
473
473
|
const conn = createMockConnection();
|
|
474
|
-
const result = await outlookMessagingProvider.sendMessage(
|
|
474
|
+
const result = await outlookMessagingProvider.sendMessage!(
|
|
475
475
|
conn,
|
|
476
476
|
"conv-id",
|
|
477
477
|
"Reply text",
|
|
@@ -493,7 +493,7 @@ describe("Outlook messaging provider", () => {
|
|
|
493
493
|
|
|
494
494
|
test("attaches files to a new message as Graph file attachments", async () => {
|
|
495
495
|
const conn = createMockConnection();
|
|
496
|
-
await outlookMessagingProvider.sendMessage(
|
|
496
|
+
await outlookMessagingProvider.sendMessage!(
|
|
497
497
|
conn,
|
|
498
498
|
"recipient@example.com",
|
|
499
499
|
"See attached",
|
|
@@ -530,7 +530,7 @@ describe("Outlook messaging provider", () => {
|
|
|
530
530
|
|
|
531
531
|
test("forwards attachments to replyToMessage when replying", async () => {
|
|
532
532
|
const conn = createMockConnection();
|
|
533
|
-
await outlookMessagingProvider.sendMessage(
|
|
533
|
+
await outlookMessagingProvider.sendMessage!(
|
|
534
534
|
conn,
|
|
535
535
|
"conv-id",
|
|
536
536
|
"Reply body",
|
|
@@ -563,7 +563,7 @@ describe("Outlook messaging provider", () => {
|
|
|
563
563
|
|
|
564
564
|
test("throws when connection is undefined", async () => {
|
|
565
565
|
await expect(
|
|
566
|
-
outlookMessagingProvider.sendMessage(
|
|
566
|
+
outlookMessagingProvider.sendMessage!(
|
|
567
567
|
undefined,
|
|
568
568
|
"recipient@example.com",
|
|
569
569
|
"Hello!",
|
|
@@ -19,7 +19,9 @@ mock.module("../providers/platform-proxy/context.js", () => ({
|
|
|
19
19
|
}),
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
|
+
const actualEnv = await import("../config/env.js");
|
|
22
23
|
mock.module("../config/env.js", () => ({
|
|
24
|
+
...actualEnv,
|
|
23
25
|
getPlatformAssistantId: () => platformAssistantId,
|
|
24
26
|
}));
|
|
25
27
|
|
|
@@ -129,6 +129,9 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
129
129
|
"../../../../daemon/embedding-reconcile.js",
|
|
130
130
|
"../../../../daemon/trust-context.js",
|
|
131
131
|
"../../../../daemon/turn-latency-sub-spans.js",
|
|
132
|
+
// mcp-setup capability cards list configured server names from the
|
|
133
|
+
// workspace mcp.json document. No plugin-api equivalent.
|
|
134
|
+
"../../../../mcp/workspace-mcp-config.js",
|
|
132
135
|
"../../../../notifications/background-failure-signal.js",
|
|
133
136
|
"../../../../persistence/checkpoints.js",
|
|
134
137
|
"../../../../persistence/conversation-types.js",
|
|
@@ -210,6 +213,16 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
210
213
|
"../../../persistence/job-handlers/message-lexical.js",
|
|
211
214
|
"../../../persistence/job-utils.js",
|
|
212
215
|
"../../../persistence/jobs-store.js",
|
|
216
|
+
// The retrospective cursor is a `(createdAt, id)` bound that must keep
|
|
217
|
+
// working after its message row is deleted. The resolver lives beside
|
|
218
|
+
// the persistence after-reads it also drives, so the plugin's
|
|
219
|
+
// accounting imports it rather than re-deriving the bound. No
|
|
220
|
+
// plugin-api equivalent.
|
|
221
|
+
"../../../persistence/message-cursor.js",
|
|
222
|
+
// Named any-state `createdAt` lookup the cursor bookkeeping uses for
|
|
223
|
+
// forks and the timestamp backfill; the messages-read boundary guard
|
|
224
|
+
// requires the read to live in persistence. No plugin-api equivalent.
|
|
225
|
+
"../../../persistence/message-reads.js",
|
|
213
226
|
"../../../persistence/raw-query.js",
|
|
214
227
|
"../../../persistence/schema/index.js",
|
|
215
228
|
"../../../prompts/persona-resolver.js",
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for resolving an app query by name, slug, or id.
|
|
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, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
import { createApp } from "../apps/app-store.js";
|
|
11
|
+
import { resolveAppQuery } from "../apps/resolve-app.js";
|
|
12
|
+
import { getWorkspacePluginsDir } from "../util/platform.js";
|
|
13
|
+
|
|
14
|
+
let workspaceDir: string;
|
|
15
|
+
let previousWorkspaceDir: string | undefined;
|
|
16
|
+
|
|
17
|
+
function freshWorkspace(): string {
|
|
18
|
+
return join(
|
|
19
|
+
tmpdir(),
|
|
20
|
+
`vellum-resolve-app-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function installPlugin(name: string): string {
|
|
25
|
+
const pluginDir = join(getWorkspacePluginsDir(), name);
|
|
26
|
+
mkdirSync(pluginDir, { recursive: true });
|
|
27
|
+
writeFileSync(
|
|
28
|
+
join(pluginDir, "package.json"),
|
|
29
|
+
JSON.stringify({ name, version: "1.0.0" }),
|
|
30
|
+
);
|
|
31
|
+
return pluginDir;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function bundleApp(pluginDir: string, app: string): void {
|
|
35
|
+
const appDir = join(pluginDir, "apps", app);
|
|
36
|
+
mkdirSync(appDir, { recursive: true });
|
|
37
|
+
writeFileSync(join(appDir, "index.html"), "<h1>Plugin app</h1>");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
previousWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
42
|
+
workspaceDir = freshWorkspace();
|
|
43
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
48
|
+
if (previousWorkspaceDir === undefined) {
|
|
49
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
50
|
+
} else {
|
|
51
|
+
process.env.VELLUM_WORKSPACE_DIR = previousWorkspaceDir;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("resolveAppQuery", () => {
|
|
56
|
+
test("resolves a workspace app by display name, slug, and id", () => {
|
|
57
|
+
const created = createApp({
|
|
58
|
+
name: "Budget",
|
|
59
|
+
schemaJson: "{}",
|
|
60
|
+
htmlDefinition: "<h1>Budget</h1>",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const byName = resolveAppQuery("Budget");
|
|
64
|
+
expect(byName.ok).toBe(true);
|
|
65
|
+
if (byName.ok) {
|
|
66
|
+
expect(byName.app.id).toBe(created.id);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const byNameCase = resolveAppQuery("budget");
|
|
70
|
+
expect(byNameCase.ok).toBe(true);
|
|
71
|
+
if (byNameCase.ok) {
|
|
72
|
+
expect(byNameCase.app.id).toBe(created.id);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const slug = created.dirName ?? "";
|
|
76
|
+
expect(slug.length).toBeGreaterThan(0);
|
|
77
|
+
const bySlug = resolveAppQuery(slug);
|
|
78
|
+
expect(bySlug.ok).toBe(true);
|
|
79
|
+
if (bySlug.ok) {
|
|
80
|
+
expect(bySlug.app.id).toBe(created.id);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const byId = resolveAppQuery(created.id);
|
|
84
|
+
expect(byId.ok).toBe(true);
|
|
85
|
+
if (byId.ok) {
|
|
86
|
+
expect(byId.app.id).toBe(created.id);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("returns not_found for an unknown name", () => {
|
|
91
|
+
const result = resolveAppQuery("missing");
|
|
92
|
+
expect(result).toEqual({
|
|
93
|
+
ok: false,
|
|
94
|
+
reason: "not_found",
|
|
95
|
+
query: "missing",
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("returns ambiguous when two apps share a display name", () => {
|
|
100
|
+
const first = createApp({
|
|
101
|
+
name: "Notes",
|
|
102
|
+
schemaJson: "{}",
|
|
103
|
+
htmlDefinition: "<h1>One</h1>",
|
|
104
|
+
});
|
|
105
|
+
const second = createApp({
|
|
106
|
+
name: "Notes",
|
|
107
|
+
schemaJson: "{}",
|
|
108
|
+
htmlDefinition: "<h1>Two</h1>",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const result = resolveAppQuery("Notes");
|
|
112
|
+
expect(result.ok).toBe(false);
|
|
113
|
+
if (!result.ok && result.reason === "ambiguous") {
|
|
114
|
+
const ids = result.matches.map((m) => m.id).sort();
|
|
115
|
+
expect(ids).toEqual([first.id, second.id].sort());
|
|
116
|
+
} else {
|
|
117
|
+
throw new Error("expected ambiguous resolution");
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("resolves a plugin app by directory name", () => {
|
|
122
|
+
const pluginDir = installPlugin("charts");
|
|
123
|
+
bundleApp(pluginDir, "viewer");
|
|
124
|
+
|
|
125
|
+
const result = resolveAppQuery("viewer");
|
|
126
|
+
expect(result.ok).toBe(true);
|
|
127
|
+
if (result.ok) {
|
|
128
|
+
expect(result.app.origin).toEqual({
|
|
129
|
+
kind: "plugin",
|
|
130
|
+
pluginName: "charts",
|
|
131
|
+
});
|
|
132
|
+
expect(result.app.id).toBe("plugins~charts~viewer");
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getCAPath,
|
|
9
9
|
issueLeafCert,
|
|
10
10
|
} from "../outbound-proxy/index.js";
|
|
11
|
+
import { writeCombinedCABundle } from "../util/ca-bundle.js";
|
|
11
12
|
|
|
12
13
|
let dataDir: string;
|
|
13
14
|
|
|
@@ -19,6 +20,40 @@ afterAll(async () => {
|
|
|
19
20
|
await rm(dataDir, { recursive: true, force: true });
|
|
20
21
|
});
|
|
21
22
|
|
|
23
|
+
test("combined trust verifies certificates from both CA sources", async () => {
|
|
24
|
+
const roots = [join(dataDir, "system"), join(dataDir, "extra")];
|
|
25
|
+
const leaves: string[] = [];
|
|
26
|
+
for (const root of roots) {
|
|
27
|
+
await ensureLocalCA(root);
|
|
28
|
+
const caDir = join(root, "proxy-ca");
|
|
29
|
+
await issueLeafCert(caDir, "example.com");
|
|
30
|
+
leaves.push(join(caDir, "issued", "example.com.pem"));
|
|
31
|
+
}
|
|
32
|
+
const bundle = join(dataDir, "combined.pem");
|
|
33
|
+
await writeCombinedCABundle(
|
|
34
|
+
getCAPath(roots[0]!),
|
|
35
|
+
getCAPath(roots[1]!),
|
|
36
|
+
bundle,
|
|
37
|
+
);
|
|
38
|
+
const verify = Bun.spawn(
|
|
39
|
+
["openssl", "verify", "-CAfile", bundle, ...leaves],
|
|
40
|
+
{
|
|
41
|
+
stdout: "pipe",
|
|
42
|
+
stderr: "pipe",
|
|
43
|
+
windowsHide: true,
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
const [code, output, error] = await Promise.all([
|
|
47
|
+
verify.exited,
|
|
48
|
+
new Response(verify.stdout).text(),
|
|
49
|
+
new Response(verify.stderr).text(),
|
|
50
|
+
]);
|
|
51
|
+
expect({ code, error }).toEqual({ code: 0, error: "" });
|
|
52
|
+
for (const leaf of leaves) {
|
|
53
|
+
expect(output).toContain(`${leaf}: OK`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
22
57
|
describe("ensureLocalCA", () => {
|
|
23
58
|
test("creates CA cert and key with correct permissions", async () => {
|
|
24
59
|
await ensureLocalCA(dataDir);
|
|
@@ -60,7 +60,7 @@ function makeConfig(overrides: Partial<AssistantConfig> = {}): AssistantConfig {
|
|
|
60
60
|
return {
|
|
61
61
|
skills: {
|
|
62
62
|
entries: {},
|
|
63
|
-
load: { extraDirs: []
|
|
63
|
+
load: { extraDirs: [] },
|
|
64
64
|
install: { nodeManager: "npm" },
|
|
65
65
|
allowBundled: null,
|
|
66
66
|
remoteProviders: {
|
|
@@ -28,7 +28,7 @@ function makeConfig(overrides: Partial<AssistantConfig> = {}): AssistantConfig {
|
|
|
28
28
|
return {
|
|
29
29
|
skills: {
|
|
30
30
|
entries: {},
|
|
31
|
-
load: { extraDirs: []
|
|
31
|
+
load: { extraDirs: [] },
|
|
32
32
|
install: { nodeManager: "npm" },
|
|
33
33
|
allowBundled: null,
|
|
34
34
|
remoteProviders: {
|
|
@@ -233,7 +233,7 @@ describe("resolveSkillStates with feature flags", () => {
|
|
|
233
233
|
const config = makeConfig({
|
|
234
234
|
skills: {
|
|
235
235
|
entries: { [DECLARED_SKILL_ID]: { enabled: true } },
|
|
236
|
-
load: { extraDirs: []
|
|
236
|
+
load: { extraDirs: [] },
|
|
237
237
|
install: { nodeManager: "npm" },
|
|
238
238
|
allowBundled: null,
|
|
239
239
|
remoteProviders: {
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for app source fingerprinting: walk exclusions, comparison, inspect
|
|
3
|
+
* statuses, and the compile-time sidecar format.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
mkdirSync,
|
|
8
|
+
mkdtempSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
symlinkSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
compareAppSourceFingerprint,
|
|
20
|
+
computeAppSourceFingerprint,
|
|
21
|
+
inspectAppSource,
|
|
22
|
+
parseAppSourceFingerprint,
|
|
23
|
+
SOURCE_FINGERPRINT_FILENAME,
|
|
24
|
+
writeAppSourceFingerprint,
|
|
25
|
+
} from "../apps/source-fingerprint.js";
|
|
26
|
+
|
|
27
|
+
let root: string;
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
root = mkdtempSync(join(tmpdir(), "app-source-fingerprint-"));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
rmSync(root, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function write(rel: string, contents: string): void {
|
|
38
|
+
const abs = join(root, rel);
|
|
39
|
+
mkdirSync(join(abs, ".."), { recursive: true });
|
|
40
|
+
writeFileSync(abs, contents);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("computeAppSourceFingerprint", () => {
|
|
44
|
+
test("digests source files with POSIX-relative keys", () => {
|
|
45
|
+
write("src/main.tsx", "export const x = 1;");
|
|
46
|
+
write("src/index.html", "<html></html>");
|
|
47
|
+
|
|
48
|
+
const fp = computeAppSourceFingerprint(root);
|
|
49
|
+
|
|
50
|
+
expect(Object.keys(fp.files).sort()).toEqual([
|
|
51
|
+
"src/index.html",
|
|
52
|
+
"src/main.tsx",
|
|
53
|
+
]);
|
|
54
|
+
expect(fp.algorithm).toBe("sha256");
|
|
55
|
+
expect(fp.files["src/main.tsx"]).toMatch(/^[0-9a-f]{64}$/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("skips dist, records, node_modules, and .git", () => {
|
|
59
|
+
write("src/main.tsx", "src");
|
|
60
|
+
write("dist/main.js", "compiled");
|
|
61
|
+
write("records/rec-1.json", "{}");
|
|
62
|
+
write("node_modules/pkg/index.js", "dep");
|
|
63
|
+
write("src/node_modules/nested/index.js", "nested");
|
|
64
|
+
write(".git/HEAD", "ref");
|
|
65
|
+
write("src/.git/config", "nope");
|
|
66
|
+
|
|
67
|
+
const fp = computeAppSourceFingerprint(root);
|
|
68
|
+
|
|
69
|
+
expect(Object.keys(fp.files)).toEqual(["src/main.tsx"]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("skips symlinks", () => {
|
|
73
|
+
write("src/real.tsx", "hello");
|
|
74
|
+
mkdirSync(join(root, "src"), { recursive: true });
|
|
75
|
+
symlinkSync(join(root, "src/real.tsx"), join(root, "src/link.tsx"));
|
|
76
|
+
|
|
77
|
+
const fp = computeAppSourceFingerprint(root);
|
|
78
|
+
|
|
79
|
+
expect(Object.keys(fp.files)).toEqual(["src/real.tsx"]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("compareAppSourceFingerprint", () => {
|
|
84
|
+
test("reports modified, added, and removed paths", () => {
|
|
85
|
+
write("src/main.tsx", "one");
|
|
86
|
+
write("src/keep.tsx", "keep");
|
|
87
|
+
const baseline = computeAppSourceFingerprint(root);
|
|
88
|
+
|
|
89
|
+
write("src/main.tsx", "two");
|
|
90
|
+
write("src/new.tsx", "new");
|
|
91
|
+
rmSync(join(root, "src/keep.tsx"));
|
|
92
|
+
|
|
93
|
+
const diff = compareAppSourceFingerprint(root, baseline);
|
|
94
|
+
expect(diff.clean).toBe(false);
|
|
95
|
+
expect(diff.modified).toEqual(["src/main.tsx"]);
|
|
96
|
+
expect(diff.added).toEqual(["src/new.tsx"]);
|
|
97
|
+
expect(diff.removed).toEqual(["src/keep.tsx"]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("is clean when source matches the baseline", () => {
|
|
101
|
+
write("src/main.tsx", "same");
|
|
102
|
+
const baseline = computeAppSourceFingerprint(root);
|
|
103
|
+
const diff = compareAppSourceFingerprint(root, baseline);
|
|
104
|
+
expect(diff.clean).toBe(true);
|
|
105
|
+
expect(diff.modified).toEqual([]);
|
|
106
|
+
expect(diff.added).toEqual([]);
|
|
107
|
+
expect(diff.removed).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("inspectAppSource", () => {
|
|
112
|
+
test("reports never_compiled when dist index is missing even if a fingerprint exists", () => {
|
|
113
|
+
write("src/main.tsx", "src");
|
|
114
|
+
mkdirSync(join(root, "dist"), { recursive: true });
|
|
115
|
+
writeAppSourceFingerprint(root, join(root, "dist"));
|
|
116
|
+
|
|
117
|
+
const result = inspectAppSource(root);
|
|
118
|
+
expect(result.status).toBe("never_compiled");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("reports unknown_baseline when dist exists without a fingerprint", () => {
|
|
122
|
+
write("src/main.tsx", "src");
|
|
123
|
+
write("dist/index.html", "<html></html>");
|
|
124
|
+
const result = inspectAppSource(root);
|
|
125
|
+
expect(result.status).toBe("unknown_baseline");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("reports clean after writing a fingerprint that matches source", () => {
|
|
129
|
+
write("src/main.tsx", "src");
|
|
130
|
+
mkdirSync(join(root, "dist"), { recursive: true });
|
|
131
|
+
writeFileSync(join(root, "dist", "index.html"), "<html></html>");
|
|
132
|
+
writeAppSourceFingerprint(root, join(root, "dist"));
|
|
133
|
+
|
|
134
|
+
const result = inspectAppSource(root);
|
|
135
|
+
expect(result.status).toBe("clean");
|
|
136
|
+
expect(result.compiledAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("reports stale after a source edit", () => {
|
|
140
|
+
write("src/main.tsx", "before");
|
|
141
|
+
mkdirSync(join(root, "dist"), { recursive: true });
|
|
142
|
+
writeFileSync(join(root, "dist", "index.html"), "<html></html>");
|
|
143
|
+
writeAppSourceFingerprint(root, join(root, "dist"));
|
|
144
|
+
write("src/main.tsx", "after");
|
|
145
|
+
|
|
146
|
+
const result = inspectAppSource(root);
|
|
147
|
+
expect(result.status).toBe("stale");
|
|
148
|
+
expect(result.modified).toEqual(["src/main.tsx"]);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("parseAppSourceFingerprint", () => {
|
|
153
|
+
test("accepts a compile sidecar with compiledAt", () => {
|
|
154
|
+
const parsed = parseAppSourceFingerprint({
|
|
155
|
+
algorithm: "sha256",
|
|
156
|
+
compiledAt: "2026-09-09T00:00:00.000Z",
|
|
157
|
+
files: { "src/main.tsx": "abc" },
|
|
158
|
+
});
|
|
159
|
+
expect(parsed).toEqual({
|
|
160
|
+
algorithm: "sha256",
|
|
161
|
+
compiledAt: "2026-09-09T00:00:00.000Z",
|
|
162
|
+
files: { "src/main.tsx": "abc" },
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("returns null for an invalid sidecar", () => {
|
|
167
|
+
expect(parseAppSourceFingerprint({ algorithm: "md5", files: {} })).toBe(
|
|
168
|
+
null,
|
|
169
|
+
);
|
|
170
|
+
expect(parseAppSourceFingerprint(null)).toBeNull();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("writeAppSourceFingerprint records a sidecar the parser accepts", () => {
|
|
174
|
+
write("src/main.tsx", "src");
|
|
175
|
+
mkdirSync(join(root, "dist"), { recursive: true });
|
|
176
|
+
writeAppSourceFingerprint(root, join(root, "dist"));
|
|
177
|
+
const raw = JSON.parse(
|
|
178
|
+
readFileSync(join(root, "dist", SOURCE_FINGERPRINT_FILENAME), "utf-8"),
|
|
179
|
+
);
|
|
180
|
+
expect(parseAppSourceFingerprint(raw)?.files["src/main.tsx"]).toMatch(
|
|
181
|
+
/^[0-9a-f]{64}$/,
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -249,14 +249,23 @@ describe("createResolveToolsCallback — toolContextPin", () => {
|
|
|
249
249
|
});
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
test("control: without a pin, a clientless fork drops
|
|
252
|
+
test("control: without a pin, a clientless fork drops client-gated tools but keeps host and UI tools", () => {
|
|
253
253
|
projectedSkillToolNames = [];
|
|
254
254
|
const resolve = createResolveToolsCallback(
|
|
255
255
|
CLIENT_GATED_DEFS,
|
|
256
256
|
clientlessExecutionCtx(),
|
|
257
257
|
)!;
|
|
258
258
|
|
|
259
|
-
|
|
259
|
+
// Host tool definitions stay on the wire for background turns, and
|
|
260
|
+
// ui_show stays because background UI surfaces persist and return. The
|
|
261
|
+
// executor rejects host tools on explicitly non-interactive turns before
|
|
262
|
+
// dispatch; ask_question and request_system_permission remain
|
|
263
|
+
// client-gated at resolution.
|
|
264
|
+
expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual([
|
|
265
|
+
"remember",
|
|
266
|
+
"host_bash",
|
|
267
|
+
"ui_show",
|
|
268
|
+
]);
|
|
260
269
|
});
|
|
261
270
|
|
|
262
271
|
test("a desktop-source pin restores the host/UI/client tool defs on the wire", () => {
|
|
@@ -291,7 +300,14 @@ describe("createResolveToolsCallback — toolContextPin", () => {
|
|
|
291
300
|
}),
|
|
292
301
|
)!;
|
|
293
302
|
|
|
294
|
-
|
|
303
|
+
// Host tool definitions stay on the wire for background turns, and
|
|
304
|
+
// ui_show survives the clientless pin: it persists and returns. The
|
|
305
|
+
// executor rejects host tools on explicitly non-interactive turns.
|
|
306
|
+
expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual([
|
|
307
|
+
"remember",
|
|
308
|
+
"host_bash",
|
|
309
|
+
"ui_show",
|
|
310
|
+
]);
|
|
295
311
|
});
|
|
296
312
|
|
|
297
313
|
test("invariant: a pinned-in tool is on the wire but can never execute", async () => {
|
|
@@ -3447,8 +3447,9 @@ describe("Subagent advisor-role consult", () => {
|
|
|
3447
3447
|
});
|
|
3448
3448
|
|
|
3449
3449
|
test("advisor consults still run when the parent conversation is gone", async () => {
|
|
3450
|
-
//
|
|
3451
|
-
//
|
|
3450
|
+
// Advisor consults do not look up the parent conversation for a skill
|
|
3451
|
+
// catalog snapshot. An evicted parent still gets a consult; the context
|
|
3452
|
+
// pack loads the catalog from disk independently.
|
|
3452
3453
|
mockFindConversation = () => undefined;
|
|
3453
3454
|
const { captured, restore } = stubSpawn();
|
|
3454
3455
|
try {
|
|
@@ -455,10 +455,11 @@ describe("resolveCallTtsProvider with requiresPcmAudio", () => {
|
|
|
455
455
|
expect(result.provider?.id).toBe("elevenlabs");
|
|
456
456
|
});
|
|
457
457
|
|
|
458
|
-
test("without requiresPcmAudio the configured provider is not swapped", async () => {
|
|
458
|
+
test("without requiresPcmAudio the configured provider is not swapped for playability", async () => {
|
|
459
459
|
testConfig.services.tts.provider = "deepgram";
|
|
460
|
-
|
|
461
|
-
// media-stream playability
|
|
460
|
+
storedKeys["deepgram"] = "dg-key";
|
|
461
|
+
// The CR transport path does not consult the media-stream playability
|
|
462
|
+
// capability.
|
|
462
463
|
registerStubProvider("deepgram", async () => {
|
|
463
464
|
return { audio: Buffer.from("x"), contentType: "audio/mpeg" };
|
|
464
465
|
});
|
|
@@ -468,6 +469,37 @@ describe("resolveCallTtsProvider with requiresPcmAudio", () => {
|
|
|
468
469
|
expect(result.useSynthesizedPath).toBe(true);
|
|
469
470
|
expect(result.audioFormat).toBe("mp3");
|
|
470
471
|
});
|
|
472
|
+
|
|
473
|
+
test("managed speech stands in for a configured provider whose credential is missing", async () => {
|
|
474
|
+
testConfig.services.tts.provider = "deepgram";
|
|
475
|
+
// No deepgram key, managed speech available: the phone speaks through
|
|
476
|
+
// managed speech, the same substitution live voice makes.
|
|
477
|
+
registerStubProvider("deepgram", async () => {
|
|
478
|
+
throw new Error("should not be used");
|
|
479
|
+
});
|
|
480
|
+
registerStubProvider("vellum", async () => {
|
|
481
|
+
return { audio: Buffer.from("x"), contentType: "audio/mpeg" };
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const result = await resolveCallTtsProvider();
|
|
485
|
+
expect(result.provider?.id).toBe("vellum");
|
|
486
|
+
expect(result.useSynthesizedPath).toBe(true);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("without managed speech a credential-less configured provider is kept", async () => {
|
|
490
|
+
mockManagedSpeechAvailable = false;
|
|
491
|
+
try {
|
|
492
|
+
testConfig.services.tts.provider = "deepgram";
|
|
493
|
+
registerStubProvider("deepgram", async () => {
|
|
494
|
+
return { audio: Buffer.from("x"), contentType: "audio/mpeg" };
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const result = await resolveCallTtsProvider();
|
|
498
|
+
expect(result.provider?.id).toBe("deepgram");
|
|
499
|
+
} finally {
|
|
500
|
+
mockManagedSpeechAvailable = true;
|
|
501
|
+
}
|
|
502
|
+
});
|
|
471
503
|
});
|
|
472
504
|
|
|
473
505
|
// ---------------------------------------------------------------------------
|
|
@@ -584,6 +616,7 @@ describe("speakSystemPrompt mid-stream failure after audio started", () => {
|
|
|
584
616
|
test("non-PCM transport: skips the native text fallback — no full-prompt re-speak", async () => {
|
|
585
617
|
testConfig.services.tts.provider = "fish-audio";
|
|
586
618
|
testConfig.services.tts.providers["fish-audio"].referenceId = "ref-123";
|
|
619
|
+
storedKeys["fish-audio"] = "fa-key";
|
|
587
620
|
|
|
588
621
|
const fishStream = jest.fn(failAfterFirstChunk());
|
|
589
622
|
registerStreamingStubProvider("fish-audio", fishStream);
|
|
@@ -657,6 +690,7 @@ describe("speakSystemPrompt aborted synthesis", () => {
|
|
|
657
690
|
test("non-PCM transport with native-fallback provider: abort skips the text fallback and end-of-turn", async () => {
|
|
658
691
|
testConfig.services.tts.provider = "fish-audio";
|
|
659
692
|
testConfig.services.tts.providers["fish-audio"].referenceId = "ref-123";
|
|
693
|
+
storedKeys["fish-audio"] = "fa-key";
|
|
660
694
|
|
|
661
695
|
const controller = new AbortController();
|
|
662
696
|
controller.abort();
|
|
@@ -742,6 +776,7 @@ describe("speakSystemPrompt aborted synthesis", () => {
|
|
|
742
776
|
test("provider AbortError without any signal is a failure — non-PCM native fallback still runs", async () => {
|
|
743
777
|
testConfig.services.tts.provider = "fish-audio";
|
|
744
778
|
testConfig.services.tts.providers["fish-audio"].referenceId = "ref-123";
|
|
779
|
+
storedKeys["fish-audio"] = "fa-key";
|
|
745
780
|
|
|
746
781
|
const fishSynthesize = jest.fn(async () => {
|
|
747
782
|
throw new DOMException("socket torn down", "AbortError");
|
|
@@ -136,6 +136,10 @@ describe("vellum-self-knowledge skill", () => {
|
|
|
136
136
|
const result = await executeSkillLoad({ skill: "vellum-self-knowledge" });
|
|
137
137
|
expect(result.content).toContain("## Critical Rule");
|
|
138
138
|
expect(result.content).toContain("Never answer from memory");
|
|
139
|
+
expect(result.content).toContain("distinct, legacy Vellum product");
|
|
140
|
+
expect(result.content).toContain(
|
|
141
|
+
"Never use it to answer questions about this Vellum",
|
|
142
|
+
);
|
|
139
143
|
});
|
|
140
144
|
|
|
141
145
|
// ── Resolution order ─────────────────────────────────────────────────
|