@vellumai/assistant 0.12.0 → 0.12.1-dev.202609141913.5e16607
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/docs/guardian-request-flow.md +16 -0
- package/openapi.yaml +292 -50
- 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 +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__/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__/notification-vellum-adapter.test.ts +85 -2
- 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 +13 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- 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__/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/events/notification-intent.ts +2 -0
- 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__/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/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/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 +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 +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/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/orphan-reaper.ts +1 -1
- 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/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-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__/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/__tests__/seed-providers-managed.test.ts +54 -0
- package/src/oauth/byo-connection.test.ts +45 -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/seed-providers.ts +165 -0
- 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 +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-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/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- 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-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__/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/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 -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/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/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/safe-env.ts +23 -3
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- 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/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
package/src/tools/acp/spawn.ts
CHANGED
|
@@ -37,6 +37,7 @@ export const acpSpawnInputSchema = z.looseObject({
|
|
|
37
37
|
agent: nullAsOmitted(z.string()),
|
|
38
38
|
task: nullAsOmitted(z.string()),
|
|
39
39
|
cwd: nullAsOmitted(z.string()),
|
|
40
|
+
model: nullAsOmitted(z.string()),
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
/**
|
|
@@ -66,6 +67,7 @@ export async function executeAcpSpawn(
|
|
|
66
67
|
}
|
|
67
68
|
const agent = parsedInput.data.agent || "claude";
|
|
68
69
|
const task = parsedInput.data.task;
|
|
70
|
+
const model = parsedInput.data.model;
|
|
69
71
|
|
|
70
72
|
if (!task) {
|
|
71
73
|
return { content: '"task" is required.', isError: true };
|
|
@@ -124,14 +126,20 @@ export async function executeAcpSpawn(
|
|
|
124
126
|
// Recheck: the auto-install and the agent-env preparation above are both
|
|
125
127
|
// awaits, and this is the point a long-lived subprocess starts.
|
|
126
128
|
throwIfCancelled(context);
|
|
127
|
-
const {
|
|
129
|
+
const {
|
|
130
|
+
acpSessionId,
|
|
131
|
+
protocolSessionId,
|
|
132
|
+
requestedModel,
|
|
133
|
+
effectiveModel,
|
|
134
|
+
modelWarning,
|
|
135
|
+
} = await manager.spawn(
|
|
128
136
|
agent,
|
|
129
137
|
agentConfig,
|
|
130
138
|
task,
|
|
131
139
|
cwd,
|
|
132
140
|
context.conversationId,
|
|
133
141
|
sendToClient,
|
|
134
|
-
context.toolUseId,
|
|
142
|
+
{ parentToolUseId: context.toolUseId, model },
|
|
135
143
|
// The manager rechecks after its own protocol handshake and session
|
|
136
144
|
// creation, so a turn stopped in that window tears the child process
|
|
137
145
|
// down instead of handing it the task.
|
|
@@ -150,16 +158,26 @@ export async function executeAcpSpawn(
|
|
|
150
158
|
const installNote = autoInstalledPackage
|
|
151
159
|
? ` Installed ${autoInstalledPackage} automatically.`
|
|
152
160
|
: "";
|
|
161
|
+
// Relayed verbatim: the warning already distinguishes an adapter with no
|
|
162
|
+
// selector from one that refused the value.
|
|
163
|
+
const modelNote = modelWarning
|
|
164
|
+
? ` The requested model was not applied: ${modelWarning}`
|
|
165
|
+
: "";
|
|
166
|
+
const effectiveModelNote = effectiveModel
|
|
167
|
+
? ` The top-level ACP session reports "${effectiveModel}" as its effective model.`
|
|
168
|
+
: " The top-level ACP session did not report an effective model.";
|
|
153
169
|
const payload = JSON.stringify({
|
|
154
170
|
acpSessionId,
|
|
155
171
|
protocolSessionId,
|
|
156
172
|
agent,
|
|
157
173
|
cwd,
|
|
174
|
+
requestedModel: requestedModel ?? null,
|
|
175
|
+
effectiveModel: effectiveModel ?? null,
|
|
158
176
|
status: "running",
|
|
159
177
|
message:
|
|
160
178
|
`ACP agent "${agent}" spawned (session: ${protocolSessionId}). ` +
|
|
161
179
|
`Results stream back via SSE. You will be notified when it completes.` +
|
|
162
|
-
`${installNote}${resumeHint}`,
|
|
180
|
+
`${installNote}${modelNote}${effectiveModelNote}${resumeHint}`,
|
|
163
181
|
});
|
|
164
182
|
|
|
165
183
|
return { content: payload, isError: false };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the `acp_status` tool's projection of session state.
|
|
3
|
+
*
|
|
4
|
+
* The state the manager tracks feeds three surfaces with different budgets:
|
|
5
|
+
* the HTTP route, the SSE picker, and this tool, whose output is spent on
|
|
6
|
+
* LLM context. Only the fields an agent can act on belong here.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
import type { AcpSessionState } from "../../acp/types.js";
|
|
12
|
+
import type { ToolContext } from "../types.js";
|
|
13
|
+
|
|
14
|
+
const RUNNING_STATE: AcpSessionState = {
|
|
15
|
+
id: "acp-1",
|
|
16
|
+
agentId: "claude",
|
|
17
|
+
acpSessionId: "proto-1",
|
|
18
|
+
parentConversationId: "conv-1",
|
|
19
|
+
status: "running",
|
|
20
|
+
startedAt: 1234,
|
|
21
|
+
task: "refactor the parser",
|
|
22
|
+
model: "claude-opus-4-5",
|
|
23
|
+
availableModels: [
|
|
24
|
+
{ value: "sonnet", label: "Sonnet" },
|
|
25
|
+
{ value: "opus", label: "Opus", description: "Most capable" },
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let statusResult: AcpSessionState | AcpSessionState[] = RUNNING_STATE;
|
|
30
|
+
|
|
31
|
+
const realAcpModule = await import("../../acp/index.js");
|
|
32
|
+
mock.module("../../acp/index.js", () => ({
|
|
33
|
+
...realAcpModule,
|
|
34
|
+
getAcpSessionManager: () => ({ getStatus: () => statusResult }),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
const { executeAcpStatus } = await import("./status.js");
|
|
38
|
+
|
|
39
|
+
const context = {
|
|
40
|
+
conversationId: "conv-1",
|
|
41
|
+
workingDir: "/tmp",
|
|
42
|
+
} as unknown as ToolContext;
|
|
43
|
+
|
|
44
|
+
describe("executeAcpStatus", () => {
|
|
45
|
+
test("a single session reports its model but not the picker", async () => {
|
|
46
|
+
statusResult = RUNNING_STATE;
|
|
47
|
+
|
|
48
|
+
const result = await executeAcpStatus({ acp_session_id: "acp-1" }, context);
|
|
49
|
+
|
|
50
|
+
expect(result.isError).toBe(false);
|
|
51
|
+
const payload = JSON.parse(result.content);
|
|
52
|
+
expect(payload).toEqual({
|
|
53
|
+
id: "acp-1",
|
|
54
|
+
agentId: "claude",
|
|
55
|
+
acpSessionId: "proto-1",
|
|
56
|
+
parentConversationId: "conv-1",
|
|
57
|
+
status: "running",
|
|
58
|
+
startedAt: 1234,
|
|
59
|
+
task: "refactor the parser",
|
|
60
|
+
model: "claude-opus-4-5",
|
|
61
|
+
});
|
|
62
|
+
expect(result.content).not.toContain("Most capable");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("the listing drops the picker from every entry", async () => {
|
|
66
|
+
statusResult = [
|
|
67
|
+
RUNNING_STATE,
|
|
68
|
+
{ ...RUNNING_STATE, id: "acp-2", model: "sonnet" },
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const result = await executeAcpStatus({}, context);
|
|
72
|
+
|
|
73
|
+
const payload = JSON.parse(result.content) as Array<
|
|
74
|
+
Record<string, unknown>
|
|
75
|
+
>;
|
|
76
|
+
expect(payload).toHaveLength(2);
|
|
77
|
+
expect(payload.map((entry) => entry.model)).toEqual([
|
|
78
|
+
"claude-opus-4-5",
|
|
79
|
+
"sonnet",
|
|
80
|
+
]);
|
|
81
|
+
for (const entry of payload) {
|
|
82
|
+
expect(entry).not.toHaveProperty("availableModels");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("an empty listing says so rather than rendering an empty array", async () => {
|
|
87
|
+
statusResult = [];
|
|
88
|
+
|
|
89
|
+
const result = await executeAcpStatus({}, context);
|
|
90
|
+
|
|
91
|
+
expect(result.content).toBe("No ACP sessions found.");
|
|
92
|
+
expect(result.isError).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
});
|
package/src/tools/acp/status.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
import { getAcpSessionManager } from "../../acp/index.js";
|
|
4
|
+
import type { AcpSessionState } from "../../acp/types.js";
|
|
4
5
|
import {
|
|
5
6
|
invalidToolInputResult,
|
|
6
7
|
nullAsOmitted,
|
|
@@ -16,6 +17,40 @@ export const acpStatusInputSchema = z.looseObject({
|
|
|
16
17
|
acp_session_id: nullAsOmitted(z.string()),
|
|
17
18
|
});
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* The session fields worth spending LLM context on. An allowlist rather than
|
|
22
|
+
* an omission: `availableModels` is a picker for the human surfaces (HTTP and
|
|
23
|
+
* SSE) that says nothing the agent can act on, and a field added to
|
|
24
|
+
* `AcpSessionState` for those surfaces should not reach this one by default.
|
|
25
|
+
* `model` stays: which model a session is running on is answerable.
|
|
26
|
+
*/
|
|
27
|
+
function projectSession(state: AcpSessionState) {
|
|
28
|
+
return {
|
|
29
|
+
id: state.id,
|
|
30
|
+
agentId: state.agentId,
|
|
31
|
+
acpSessionId: state.acpSessionId,
|
|
32
|
+
parentConversationId: state.parentConversationId,
|
|
33
|
+
status: state.status,
|
|
34
|
+
startedAt: state.startedAt,
|
|
35
|
+
completedAt: state.completedAt,
|
|
36
|
+
error: state.error,
|
|
37
|
+
stopReason: state.stopReason,
|
|
38
|
+
task: state.task,
|
|
39
|
+
parentToolUseId: state.parentToolUseId,
|
|
40
|
+
authErrorCode: state.authErrorCode,
|
|
41
|
+
authErrorCredential: state.authErrorCredential,
|
|
42
|
+
latestUsage: state.latestUsage,
|
|
43
|
+
model: state.model,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Projects either shape `getStatus` answers with. */
|
|
48
|
+
function projectStatus(status: AcpSessionState | AcpSessionState[]): unknown {
|
|
49
|
+
return Array.isArray(status)
|
|
50
|
+
? status.map(projectSession)
|
|
51
|
+
: projectSession(status);
|
|
52
|
+
}
|
|
53
|
+
|
|
19
54
|
export async function executeAcpStatus(
|
|
20
55
|
input: Record<string, unknown>,
|
|
21
56
|
_context: ToolContext,
|
|
@@ -29,9 +64,8 @@ export async function executeAcpStatus(
|
|
|
29
64
|
|
|
30
65
|
try {
|
|
31
66
|
if (acpSessionId) {
|
|
32
|
-
const state = manager.getStatus(acpSessionId);
|
|
33
67
|
return {
|
|
34
|
-
content: JSON.stringify(
|
|
68
|
+
content: JSON.stringify(projectStatus(manager.getStatus(acpSessionId))),
|
|
35
69
|
isError: false,
|
|
36
70
|
};
|
|
37
71
|
}
|
|
@@ -42,7 +76,10 @@ export async function executeAcpStatus(
|
|
|
42
76
|
return { content: "No ACP sessions found.", isError: false };
|
|
43
77
|
}
|
|
44
78
|
|
|
45
|
-
return {
|
|
79
|
+
return {
|
|
80
|
+
content: JSON.stringify(projectStatus(allStates)),
|
|
81
|
+
isError: false,
|
|
82
|
+
};
|
|
46
83
|
} catch (err) {
|
|
47
84
|
const msg = err instanceof Error ? err.message : String(err);
|
|
48
85
|
return { content: msg, isError: true };
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* the proxy resolver, which forwards the call to the connected client.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { formatDesktopAppRequired } from "../capability-offer.js";
|
|
8
9
|
import { throwIfCancelled } from "../shared/abort.js";
|
|
9
10
|
import type { ToolContext, ToolExecutionResult } from "../types.js";
|
|
10
11
|
|
|
@@ -32,7 +33,7 @@ export function forwardAppControlProxyTool(
|
|
|
32
33
|
}
|
|
33
34
|
if (!context.proxyToolResolver) {
|
|
34
35
|
return Promise.resolve({
|
|
35
|
-
content:
|
|
36
|
+
content: formatDesktopAppRequired("apps"),
|
|
36
37
|
isError: true,
|
|
37
38
|
});
|
|
38
39
|
}
|
|
@@ -4,11 +4,26 @@ import {
|
|
|
4
4
|
appendLoggedInBrowserOffer,
|
|
5
5
|
CHROME_WEB_STORE_INSTALL_URL,
|
|
6
6
|
DESKTOP_APP_DOWNLOAD_URL,
|
|
7
|
+
formatDesktopAppRequired,
|
|
7
8
|
formatLoggedInBrowserOffer,
|
|
8
9
|
isPhoneSurface,
|
|
9
10
|
shouldOfferLoggedInBrowser,
|
|
10
11
|
} from "./capability-offer.js";
|
|
11
12
|
|
|
13
|
+
describe("formatDesktopAppRequired", () => {
|
|
14
|
+
test("offers a direct desktop-app download and retry step", () => {
|
|
15
|
+
const message = formatDesktopAppRequired("shell");
|
|
16
|
+
|
|
17
|
+
expect(message).toContain(
|
|
18
|
+
"The Vellum desktop app is needed to run commands on your computer",
|
|
19
|
+
);
|
|
20
|
+
expect(message).toContain(DESKTOP_APP_DOWNLOAD_URL);
|
|
21
|
+
expect(message).toContain("Open it, then retry.");
|
|
22
|
+
expect(message).not.toContain("client");
|
|
23
|
+
expect(message).not.toContain("daemon");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
12
27
|
describe("isPhoneSurface", () => {
|
|
13
28
|
test("is true for iOS transport and mobile client OS", () => {
|
|
14
29
|
expect(isPhoneSurface({ transportInterface: "ios" })).toBe(true);
|
|
@@ -49,7 +64,9 @@ describe("shouldOfferLoggedInBrowser", () => {
|
|
|
49
64
|
expect(shouldOfferLoggedInBrowser('Invalid browser_mode "bogus"')).toBe(
|
|
50
65
|
false,
|
|
51
66
|
);
|
|
52
|
-
expect(shouldOfferLoggedInBrowser("url must use http or https")).toBe(
|
|
67
|
+
expect(shouldOfferLoggedInBrowser("url must use http or https")).toBe(
|
|
68
|
+
false,
|
|
69
|
+
);
|
|
53
70
|
});
|
|
54
71
|
});
|
|
55
72
|
|
|
@@ -71,6 +88,7 @@ describe("formatLoggedInBrowserOffer", () => {
|
|
|
71
88
|
});
|
|
72
89
|
expect(offer).toContain("no in-app browser");
|
|
73
90
|
expect(offer).toContain("Do not describe a browser panel");
|
|
91
|
+
expect(offer).toContain("Mac or Windows PC");
|
|
74
92
|
expect(offer).toContain(DESKTOP_APP_DOWNLOAD_URL);
|
|
75
93
|
expect(offer).toContain(CHROME_WEB_STORE_INSTALL_URL);
|
|
76
94
|
});
|
|
@@ -78,7 +96,9 @@ describe("formatLoggedInBrowserOffer", () => {
|
|
|
78
96
|
|
|
79
97
|
describe("appendLoggedInBrowserOffer", () => {
|
|
80
98
|
test("appends the offer once", () => {
|
|
81
|
-
const once = appendLoggedInBrowserOffer(
|
|
99
|
+
const once = appendLoggedInBrowserOffer(
|
|
100
|
+
"Error: Navigation failed: timeout",
|
|
101
|
+
);
|
|
82
102
|
expect(once).toContain("Error: Navigation failed: timeout");
|
|
83
103
|
expect(once).toContain(DESKTOP_APP_DOWNLOAD_URL);
|
|
84
104
|
const twice = appendLoggedInBrowserOffer(once);
|
|
@@ -7,7 +7,22 @@ export const DESKTOP_APP_DOWNLOAD_URL = "https://www.vellum.ai/downloads";
|
|
|
7
7
|
export const CHROME_WEB_STORE_INSTALL_URL =
|
|
8
8
|
"https://chromewebstore.google.com/detail/vellum-assistant-browser/hphbdmpffeigpcdjkckleobjmhhokpne";
|
|
9
9
|
|
|
10
|
-
export const DESKTOP_APP_INSTALL_HINT = `
|
|
10
|
+
export const DESKTOP_APP_INSTALL_HINT = `Download the Vellum desktop app: ${DESKTOP_APP_DOWNLOAD_URL}. Open it, then retry.`;
|
|
11
|
+
|
|
12
|
+
export type DesktopAppCapability = "files" | "screen" | "shell" | "apps";
|
|
13
|
+
|
|
14
|
+
const DESKTOP_APP_CAPABILITY_ACTION: Record<DesktopAppCapability, string> = {
|
|
15
|
+
files: "access files on your computer",
|
|
16
|
+
screen: "view or control your screen",
|
|
17
|
+
shell: "run commands on your computer",
|
|
18
|
+
apps: "control apps on your computer",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function formatDesktopAppRequired(
|
|
22
|
+
capability: DesktopAppCapability,
|
|
23
|
+
): string {
|
|
24
|
+
return `Error: The Vellum desktop app is needed to ${DESKTOP_APP_CAPABILITY_ACTION[capability]}. ${DESKTOP_APP_INSTALL_HINT}`;
|
|
25
|
+
}
|
|
11
26
|
|
|
12
27
|
/** User-facing install step for status userActions and command-failure hints. */
|
|
13
28
|
export const CHROME_EXTENSION_INSTALL_HINT = `Install the Vellum Assistant Chrome extension from the Chrome Web Store: ${CHROME_WEB_STORE_INSTALL_URL}`;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { RiskLevel } from "../../permissions/types.js";
|
|
11
|
+
import { formatDesktopAppRequired } from "../capability-offer.js";
|
|
11
12
|
import type {
|
|
12
13
|
ToolContext,
|
|
13
14
|
ToolDefinition,
|
|
@@ -31,7 +32,7 @@ function proxyExecute(toolName: string) {
|
|
|
31
32
|
): Promise<ToolExecutionResult> => {
|
|
32
33
|
if (!context.proxyToolResolver) {
|
|
33
34
|
return {
|
|
34
|
-
content:
|
|
35
|
+
content: formatDesktopAppRequired("screen"),
|
|
35
36
|
isError: true,
|
|
36
37
|
};
|
|
37
38
|
}
|
|
@@ -456,7 +457,8 @@ export const computerUseObserveTool = {
|
|
|
456
457
|
type: "integer",
|
|
457
458
|
minimum: 1,
|
|
458
459
|
maximum: 4294967295,
|
|
459
|
-
description:
|
|
460
|
+
description:
|
|
461
|
+
"macOS only: capture this native CGWindowID instead of the desktop, including only its accessibility tree. Obtain a current native window ID first; do not guess or use a browser tab ID. Applies to this observation only, not subsequent actions. Requires a desktop helper with window-capture support. Screenshot coordinates are window-relative; use accessibility element IDs for later actions, not desktop scaling.",
|
|
460
462
|
},
|
|
461
463
|
target_client_id: {
|
|
462
464
|
type: "string",
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* the proxy resolver, which forwards the call to the connected desktop client.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { formatDesktopAppRequired } from "../capability-offer.js";
|
|
8
9
|
import { throwIfCancelled } from "../shared/abort.js";
|
|
9
10
|
import type { ToolContext, ToolExecutionResult } from "../types.js";
|
|
10
11
|
|
|
@@ -45,7 +46,7 @@ export function forwardComputerUseProxyTool(
|
|
|
45
46
|
}
|
|
46
47
|
if (!context.proxyToolResolver) {
|
|
47
48
|
return Promise.resolve({
|
|
48
|
-
content:
|
|
49
|
+
content: formatDesktopAppRequired("screen"),
|
|
49
50
|
isError: true,
|
|
50
51
|
});
|
|
51
52
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "@vellumai/credential-storage";
|
|
21
21
|
import type { CredentialRecord } from "@vellumai/service-contracts/credential-rpc";
|
|
22
22
|
|
|
23
|
+
import { createCesHttpRecordBackendIfConfigured } from "../../security/ces-http-record-backend.js";
|
|
23
24
|
import type { CredentialRecordBackend } from "../../security/ces-rpc-record-backend.js";
|
|
24
25
|
import { getLogger } from "../../util/logger.js";
|
|
25
26
|
import type { CredentialInjectionTemplate } from "./policy-types.js";
|
|
@@ -55,6 +56,11 @@ const CES_MEMORY_PATH = "ces-in-memory-credential-metadata";
|
|
|
55
56
|
let _store: StaticCredentialMetadataStore | undefined;
|
|
56
57
|
let _overridePath: string | null = null;
|
|
57
58
|
let _recordBackend: CredentialRecordBackend | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Test override for the CES HTTP listing fallback. `undefined` uses the
|
|
61
|
+
* production factory. `null` disables HTTP fallback.
|
|
62
|
+
*/
|
|
63
|
+
let _httpRecordBackendOverride: CredentialRecordBackend | null | undefined;
|
|
58
64
|
let _cesRecordsLoaded = false;
|
|
59
65
|
let _cesLoad: Promise<void> | undefined;
|
|
60
66
|
let _cesLoadGeneration = 0;
|
|
@@ -255,6 +261,93 @@ export function listCredentialMetadata(): CredentialMetadata[] {
|
|
|
255
261
|
return getStore().list() as CredentialMetadata[];
|
|
256
262
|
}
|
|
257
263
|
|
|
264
|
+
export type CredentialRecordListResult = {
|
|
265
|
+
records: CredentialMetadata[];
|
|
266
|
+
unreachable: boolean;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
type ListedCredentialRecords = Array<{
|
|
270
|
+
account: string;
|
|
271
|
+
record: CredentialRecord;
|
|
272
|
+
}>;
|
|
273
|
+
|
|
274
|
+
function backendIsAvailable(backend: CredentialRecordBackend): boolean {
|
|
275
|
+
try {
|
|
276
|
+
return backend.isAvailable();
|
|
277
|
+
} catch {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function listFromRecordBackend(
|
|
283
|
+
backend: CredentialRecordBackend,
|
|
284
|
+
): Promise<ListedCredentialRecords | null> {
|
|
285
|
+
if (!backendIsAvailable(backend)) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
return backend.list();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function resolveHttpRecordBackend(): CredentialRecordBackend | null {
|
|
292
|
+
if (_httpRecordBackendOverride !== undefined) {
|
|
293
|
+
return _httpRecordBackendOverride;
|
|
294
|
+
}
|
|
295
|
+
return createCesHttpRecordBackendIfConfigured();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function listFromHttpFallback(): Promise<ListedCredentialRecords | null> {
|
|
299
|
+
const httpBackend = resolveHttpRecordBackend();
|
|
300
|
+
if (!httpBackend) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
return listFromRecordBackend(httpBackend);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Live catalog from the credential record backend. Does not read the
|
|
308
|
+
* daemon in-process cache, so an outage cannot be mistaken for an empty
|
|
309
|
+
* vault. `unreachable` is true when the backend is missing, down, or
|
|
310
|
+
* the list RPC fails, and CES HTTP failover is also unavailable.
|
|
311
|
+
*
|
|
312
|
+
* When the attached backend is CES RPC (or none is attached), listing
|
|
313
|
+
* fails over to CES HTTP in containerized deployments so a down RPC
|
|
314
|
+
* socket does not hide a healthy vault sidecar. A successful empty
|
|
315
|
+
* catalog (`[]`) does not fall through to HTTP.
|
|
316
|
+
*
|
|
317
|
+
* The file-backed test store is the catalog when a test override path is set.
|
|
318
|
+
*/
|
|
319
|
+
export async function listCredentialRecordsLive(): Promise<CredentialRecordListResult> {
|
|
320
|
+
if (_overridePath) {
|
|
321
|
+
return {
|
|
322
|
+
records: getStore().list() as CredentialMetadata[],
|
|
323
|
+
unreachable: false,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
if (_recordBackend) {
|
|
327
|
+
const remote = await listFromRecordBackend(_recordBackend);
|
|
328
|
+
if (remote !== null) {
|
|
329
|
+
return {
|
|
330
|
+
records: remote.map((entry) => entry.record as CredentialMetadata),
|
|
331
|
+
unreachable: false,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (_recordBackend.name !== "ces-rpc") {
|
|
335
|
+
return { records: [], unreachable: true };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const httpRemote = await listFromHttpFallback();
|
|
339
|
+
if (httpRemote !== null) {
|
|
340
|
+
log.warn(
|
|
341
|
+
"CES RPC credential record list unavailable. Failing over to CES HTTP.",
|
|
342
|
+
);
|
|
343
|
+
return {
|
|
344
|
+
records: httpRemote.map((entry) => entry.record as CredentialMetadata),
|
|
345
|
+
unreachable: false,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
return { records: [], unreachable: true };
|
|
349
|
+
}
|
|
350
|
+
|
|
258
351
|
/**
|
|
259
352
|
* Delete metadata for a credential.
|
|
260
353
|
*/
|
|
@@ -287,3 +380,10 @@ export function _setMetadataPath(path: string | null): void {
|
|
|
287
380
|
}
|
|
288
381
|
}
|
|
289
382
|
}
|
|
383
|
+
|
|
384
|
+
/** @internal Test-only: override the CES HTTP listing fallback. */
|
|
385
|
+
export function _setHttpRecordBackendForTests(
|
|
386
|
+
backend: CredentialRecordBackend | null | undefined,
|
|
387
|
+
): void {
|
|
388
|
+
_httpRecordBackendOverride = backend;
|
|
389
|
+
}
|
|
@@ -71,8 +71,9 @@ describe("host_file_edit cross-client guards", () => {
|
|
|
71
71
|
);
|
|
72
72
|
expect(result.isError).toBe(true);
|
|
73
73
|
expect(result.content).toContain(
|
|
74
|
-
"
|
|
74
|
+
"The Vellum desktop app is needed to access files on your computer",
|
|
75
75
|
);
|
|
76
|
+
expect(result.content).toContain("https://www.vellum.ai/downloads");
|
|
76
77
|
});
|
|
77
78
|
|
|
78
79
|
test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
|
|
@@ -4,7 +4,7 @@ import { supportsHostProxy } from "../../channels/types.js";
|
|
|
4
4
|
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
|
|
5
5
|
import { RiskLevel } from "../../permissions/types.js";
|
|
6
6
|
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
|
|
7
|
-
import {
|
|
7
|
+
import { formatDesktopAppRequired } from "../capability-offer.js";
|
|
8
8
|
import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
|
|
9
9
|
import { formatEditDiff } from "../shared/filesystem/format-diff.js";
|
|
10
10
|
import { hostPolicy } from "../shared/filesystem/path-policy.js";
|
|
@@ -115,7 +115,7 @@ export const hostFileEditTool = {
|
|
|
115
115
|
!HostFileProxy.instance.isAvailable()
|
|
116
116
|
) {
|
|
117
117
|
return {
|
|
118
|
-
content:
|
|
118
|
+
content: formatDesktopAppRequired("files"),
|
|
119
119
|
isError: true,
|
|
120
120
|
};
|
|
121
121
|
}
|
|
@@ -67,8 +67,9 @@ describe("host_file_read cross-client guards", () => {
|
|
|
67
67
|
);
|
|
68
68
|
expect(result.isError).toBe(true);
|
|
69
69
|
expect(result.content).toContain(
|
|
70
|
-
"
|
|
70
|
+
"The Vellum desktop app is needed to access files on your computer",
|
|
71
71
|
);
|
|
72
|
+
expect(result.content).toContain("https://www.vellum.ai/downloads");
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
|
|
@@ -6,7 +6,7 @@ import { supportsHostProxy } from "../../channels/types.js";
|
|
|
6
6
|
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
|
|
7
7
|
import { RiskLevel } from "../../permissions/types.js";
|
|
8
8
|
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
|
|
9
|
-
import {
|
|
9
|
+
import { formatDesktopAppRequired } from "../capability-offer.js";
|
|
10
10
|
import {
|
|
11
11
|
AUDIO_EXTENSIONS,
|
|
12
12
|
readAudioFile,
|
|
@@ -121,7 +121,7 @@ export const hostFileReadTool = {
|
|
|
121
121
|
!HostFileProxy.instance.isAvailable()
|
|
122
122
|
) {
|
|
123
123
|
return {
|
|
124
|
-
content:
|
|
124
|
+
content: formatDesktopAppRequired("files"),
|
|
125
125
|
isError: true,
|
|
126
126
|
};
|
|
127
127
|
}
|
|
@@ -335,8 +335,9 @@ describe("host_file_transfer cross-client guards", () => {
|
|
|
335
335
|
|
|
336
336
|
expect(result.isError).toBe(true);
|
|
337
337
|
expect(result.content).toContain(
|
|
338
|
-
"
|
|
338
|
+
"The Vellum desktop app is needed to access files on your computer",
|
|
339
339
|
);
|
|
340
|
+
expect(result.content).toContain("https://www.vellum.ai/downloads");
|
|
340
341
|
expect(toSandboxCalls.length).toBe(0);
|
|
341
342
|
});
|
|
342
343
|
|
|
@@ -6,7 +6,7 @@ import { supportsHostProxy } from "../../channels/types.js";
|
|
|
6
6
|
import { HostTransferProxy } from "../../daemon/host-transfer-proxy.js";
|
|
7
7
|
import { RiskLevel } from "../../permissions/types.js";
|
|
8
8
|
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
|
|
9
|
-
import {
|
|
9
|
+
import { formatDesktopAppRequired } from "../capability-offer.js";
|
|
10
10
|
import { declareDaemonActivityField } from "../schema-transforms.js";
|
|
11
11
|
import { sandboxPolicy } from "../shared/filesystem/path-policy.js";
|
|
12
12
|
import type {
|
|
@@ -126,7 +126,7 @@ export const hostFileTransferTool = {
|
|
|
126
126
|
!HostTransferProxy.instance.isAvailable()
|
|
127
127
|
) {
|
|
128
128
|
return {
|
|
129
|
-
content:
|
|
129
|
+
content: formatDesktopAppRequired("files"),
|
|
130
130
|
isError: true,
|
|
131
131
|
};
|
|
132
132
|
}
|
|
@@ -67,8 +67,9 @@ describe("host_file_write cross-client guards", () => {
|
|
|
67
67
|
);
|
|
68
68
|
expect(result.isError).toBe(true);
|
|
69
69
|
expect(result.content).toContain(
|
|
70
|
-
"
|
|
70
|
+
"The Vellum desktop app is needed to access files on your computer",
|
|
71
71
|
);
|
|
72
|
+
expect(result.content).toContain("https://www.vellum.ai/downloads");
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
|
|
@@ -4,7 +4,7 @@ import { supportsHostProxy } from "../../channels/types.js";
|
|
|
4
4
|
import { HostFileProxy } from "../../daemon/host-file-proxy.js";
|
|
5
5
|
import { RiskLevel } from "../../permissions/types.js";
|
|
6
6
|
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
|
|
7
|
-
import {
|
|
7
|
+
import { formatDesktopAppRequired } from "../capability-offer.js";
|
|
8
8
|
import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
|
|
9
9
|
import { formatWriteSummary } from "../shared/filesystem/format-diff.js";
|
|
10
10
|
import { hostPolicy } from "../shared/filesystem/path-policy.js";
|
|
@@ -90,7 +90,7 @@ export const hostFileWriteTool = {
|
|
|
90
90
|
!HostFileProxy.instance.isAvailable()
|
|
91
91
|
) {
|
|
92
92
|
return {
|
|
93
|
-
content:
|
|
93
|
+
content: formatDesktopAppRequired("files"),
|
|
94
94
|
isError: true,
|
|
95
95
|
};
|
|
96
96
|
}
|
|
@@ -170,6 +170,22 @@ afterEach(() => {
|
|
|
170
170
|
mockProxyAvailable = false;
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
+
describe("host_bash unavailable desktop-app recovery", () => {
|
|
174
|
+
test("offers the desktop app download when a web turn has no host connection", async () => {
|
|
175
|
+
const result = await hostShellTool.execute(
|
|
176
|
+
{ command: "pwd" },
|
|
177
|
+
makeContext({ transportInterface: "web" }),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
expect(result.isError).toBe(true);
|
|
181
|
+
expect(result.content).toContain(
|
|
182
|
+
"The Vellum desktop app is needed to run commands on your computer",
|
|
183
|
+
);
|
|
184
|
+
expect(result.content).toContain("https://www.vellum.ai/downloads");
|
|
185
|
+
expect(result.content).toContain("Open it, then retry.");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
173
189
|
// ---------------------------------------------------------------------------
|
|
174
190
|
// Proxy path — background: true
|
|
175
191
|
// ---------------------------------------------------------------------------
|