@vellumai/assistant 0.12.1 → 0.12.2-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/ARCHITECTURE.md +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
- package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
- package/openapi.yaml +360 -51
- package/package.json +3 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +19 -7
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +250 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/cu-unified-flow.test.ts +14 -4
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/heartbeat-service.test.ts +56 -40
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-cu-proxy.test.ts +596 -0
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/oauth-commands-routes.test.ts +168 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
- package/src/__tests__/service-account-token.test.ts +247 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/ui-channel-variants.test.ts +1 -56
- package/src/__tests__/unicode.test.ts +13 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
- package/src/acp/__tests__/acp-credentials.test.ts +13 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/acp-claude-oauth.ts +328 -14
- package/src/acp/acp-credentials.ts +19 -0
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +96 -10
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/claude-token-refresh.ts +150 -0
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/prepare-agent-env.ts +21 -6
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/agent/loop.ts +1 -0
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/events/notification-intent.ts +2 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/avatar/identity-avatar.ts +7 -2
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +155 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +92 -72
- package/src/calls/voice-triage-escalate.ts +178 -59
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/oauth/status.ts +5 -0
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +33 -13
- package/src/config/loader.ts +2 -0
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/services.ts +18 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/credential-health/credential-health-service.ts +11 -17
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-runtime-assembly.ts +20 -7
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +21 -10
- package/src/daemon/conversation-tool-setup.ts +45 -59
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-cu-proxy.ts +224 -19
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/reaction-history-render.ts +2 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +122 -0
- package/src/desktop/desktop-chrome-policy.test.ts +79 -0
- package/src/desktop/desktop-chrome-policy.ts +47 -0
- package/src/desktop/desktop-chrome-session.ts +37 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +308 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +730 -0
- package/src/desktop/desktop-session-manager.ts +850 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-drag.ts +177 -0
- package/src/desktop/desktop-window-manager-config.test.ts +230 -0
- package/src/desktop/desktop-window-manager-config.ts +238 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/heartbeat/heartbeat-service.ts +21 -3
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/protocol.test.ts +39 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/__tests__/session-controls.test.ts +79 -0
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +297 -699
- package/src/live-voice/protocol.ts +60 -2
- package/src/live-voice/session-controls.ts +111 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
- package/src/oauth/byo-connection.test.ts +105 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +100 -0
- package/src/oauth/connection-resolver.ts +120 -3
- package/src/oauth/provider-serializer.ts +9 -0
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +314 -27
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-queries.ts +2 -2
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +4 -2
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/host-utils.ts +10 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +28 -2
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/__tests__/retry-callsite.test.ts +41 -0
- package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/endpoint-probe.ts +10 -1
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +212 -79
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/providers/opencode/client.test.ts +73 -4
- package/src/providers/opencode/client.ts +60 -3
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +30 -19
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +3 -1
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/host-observe.test.ts +40 -1
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/host-observe.ts +11 -1
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +90 -2
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/conversation-routes.ts +16 -3
- package/src/runtime/routes/credential-routes.ts +20 -14
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +61 -4
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/index.ts +4 -2
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +6 -20
- package/src/runtime/routes/oauth-commands-routes.ts +50 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/security/untrusted-content.ts +3 -1
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +121 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/filesystem/search.ts +2 -1
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/network/web-search.ts +6 -1
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/ui-surface/channel-variants.ts +10 -59
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/types/host-capabilities.ts +5 -3
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/util/unicode.ts +15 -1
- package/src/watch/watch-retro.ts +6 -8
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +6 -55
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getPlatformBaseUrl,
|
|
5
|
+
setPlatformAssistantId,
|
|
6
|
+
setPlatformBaseUrl,
|
|
7
|
+
setPlatformOrganizationId,
|
|
8
|
+
setPlatformUserId,
|
|
9
|
+
} from "../env.js";
|
|
10
|
+
|
|
11
|
+
const actualSecureKeys = await import("../../security/secure-keys.js");
|
|
12
|
+
mock.module("../../security/secure-keys.js", () => ({
|
|
13
|
+
...actualSecureKeys,
|
|
14
|
+
getSecureKeyAsync: async () => undefined,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
_resetPlatformIdentityEnsureForTests,
|
|
19
|
+
fetchPlatformIdentityIds,
|
|
20
|
+
PLATFORM_IDENTITY_VALIDATE_PATH,
|
|
21
|
+
resolvePlatformAssistantId,
|
|
22
|
+
} = await import("../platform-identity.js");
|
|
23
|
+
|
|
24
|
+
const BASE_URL = "https://platform.vellum.ai";
|
|
25
|
+
const ASSISTANT_ID = "11111111-2222-4333-8444-555555555555";
|
|
26
|
+
const ORG_ID = "22222222-3333-4444-8555-666666666666";
|
|
27
|
+
const USER_ID = "33333333-4444-4555-8666-777777777777";
|
|
28
|
+
|
|
29
|
+
describe("fetchPlatformIdentityIds", () => {
|
|
30
|
+
const originalFetch = globalThis.fetch;
|
|
31
|
+
const fetchCalls: Array<{ url: string; init?: RequestInit }> = [];
|
|
32
|
+
let fetchImpl: (
|
|
33
|
+
input: RequestInfo | URL,
|
|
34
|
+
init?: RequestInit,
|
|
35
|
+
) => Promise<Response> = async () =>
|
|
36
|
+
new Response("not found", { status: 404 });
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
fetchCalls.length = 0;
|
|
40
|
+
fetchImpl = async () => new Response("not found", { status: 404 });
|
|
41
|
+
globalThis.fetch = (async (
|
|
42
|
+
input: RequestInfo | URL,
|
|
43
|
+
init?: RequestInit,
|
|
44
|
+
) => {
|
|
45
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
46
|
+
fetchCalls.push({ url, init });
|
|
47
|
+
return fetchImpl(input, init);
|
|
48
|
+
}) as unknown as typeof fetch;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
globalThis.fetch = originalFetch;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("returns the three ids from a successful validate response", async () => {
|
|
56
|
+
fetchImpl = async () =>
|
|
57
|
+
new Response(
|
|
58
|
+
JSON.stringify({
|
|
59
|
+
assistant_id: ASSISTANT_ID,
|
|
60
|
+
organization_id: ORG_ID,
|
|
61
|
+
user_id: USER_ID,
|
|
62
|
+
name: "Example Assistant",
|
|
63
|
+
}),
|
|
64
|
+
{ status: 200 },
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
await expect(
|
|
68
|
+
fetchPlatformIdentityIds(BASE_URL, "assistant-key"),
|
|
69
|
+
).resolves.toEqual({
|
|
70
|
+
assistantId: ASSISTANT_ID,
|
|
71
|
+
organizationId: ORG_ID,
|
|
72
|
+
userId: USER_ID,
|
|
73
|
+
});
|
|
74
|
+
expect(fetchCalls[0]?.url).toBe(
|
|
75
|
+
`${BASE_URL}${PLATFORM_IDENTITY_VALIDATE_PATH}`,
|
|
76
|
+
);
|
|
77
|
+
expect(fetchCalls[0]?.init?.method).toBe("POST");
|
|
78
|
+
const headers = new Headers(fetchCalls[0]?.init?.headers);
|
|
79
|
+
expect(headers.get("Authorization")).toBe("Api-Key assistant-key");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("strips a trailing slash from the base URL", async () => {
|
|
83
|
+
fetchImpl = async () =>
|
|
84
|
+
new Response(
|
|
85
|
+
JSON.stringify({
|
|
86
|
+
assistant_id: ASSISTANT_ID,
|
|
87
|
+
organization_id: ORG_ID,
|
|
88
|
+
user_id: USER_ID,
|
|
89
|
+
}),
|
|
90
|
+
{ status: 200 },
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
await fetchPlatformIdentityIds(`${BASE_URL}/`, "assistant-key");
|
|
94
|
+
expect(fetchCalls[0]?.url).toBe(
|
|
95
|
+
`${BASE_URL}${PLATFORM_IDENTITY_VALIDATE_PATH}`,
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("returns null on 401 without throwing", async () => {
|
|
100
|
+
fetchImpl = async () => new Response("no", { status: 401 });
|
|
101
|
+
await expect(
|
|
102
|
+
fetchPlatformIdentityIds(BASE_URL, "assistant-key"),
|
|
103
|
+
).resolves.toBeNull();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("returns null on a network error without throwing", async () => {
|
|
107
|
+
fetchImpl = async () => {
|
|
108
|
+
throw new Error("network down");
|
|
109
|
+
};
|
|
110
|
+
await expect(
|
|
111
|
+
fetchPlatformIdentityIds(BASE_URL, "assistant-key"),
|
|
112
|
+
).resolves.toBeNull();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("resolvePlatformAssistantId", () => {
|
|
117
|
+
const originalFetch = globalThis.fetch;
|
|
118
|
+
const originalAssistantApiKeyEnv = process.env.ASSISTANT_API_KEY;
|
|
119
|
+
const fetchCalls: Array<{ url: string; init?: RequestInit }> = [];
|
|
120
|
+
let fetchImpl: (
|
|
121
|
+
input: RequestInfo | URL,
|
|
122
|
+
init?: RequestInit,
|
|
123
|
+
) => Promise<Response> = async () =>
|
|
124
|
+
new Response("not found", { status: 404 });
|
|
125
|
+
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
fetchCalls.length = 0;
|
|
128
|
+
fetchImpl = async () => new Response("not found", { status: 404 });
|
|
129
|
+
globalThis.fetch = (async (
|
|
130
|
+
input: RequestInfo | URL,
|
|
131
|
+
init?: RequestInit,
|
|
132
|
+
) => {
|
|
133
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
134
|
+
fetchCalls.push({ url, init });
|
|
135
|
+
return fetchImpl(input, init);
|
|
136
|
+
}) as unknown as typeof fetch;
|
|
137
|
+
delete process.env.ASSISTANT_API_KEY;
|
|
138
|
+
setPlatformBaseUrl(undefined);
|
|
139
|
+
setPlatformAssistantId(undefined);
|
|
140
|
+
setPlatformOrganizationId(undefined);
|
|
141
|
+
setPlatformUserId(undefined);
|
|
142
|
+
_resetPlatformIdentityEnsureForTests();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
afterEach(() => {
|
|
146
|
+
globalThis.fetch = originalFetch;
|
|
147
|
+
if (originalAssistantApiKeyEnv === undefined) {
|
|
148
|
+
delete process.env.ASSISTANT_API_KEY;
|
|
149
|
+
} else {
|
|
150
|
+
process.env.ASSISTANT_API_KEY = originalAssistantApiKeyEnv;
|
|
151
|
+
}
|
|
152
|
+
setPlatformBaseUrl(undefined);
|
|
153
|
+
setPlatformAssistantId(undefined);
|
|
154
|
+
setPlatformOrganizationId(undefined);
|
|
155
|
+
setPlatformUserId(undefined);
|
|
156
|
+
_resetPlatformIdentityEnsureForTests();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("returns the in-memory override without calling validate", async () => {
|
|
160
|
+
setPlatformAssistantId(ASSISTANT_ID);
|
|
161
|
+
process.env.ASSISTANT_API_KEY = "assistant-key";
|
|
162
|
+
await expect(resolvePlatformAssistantId()).resolves.toBe(ASSISTANT_ID);
|
|
163
|
+
expect(fetchCalls).toHaveLength(0);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("returns empty when the in-memory override is unset and there is no API key", async () => {
|
|
167
|
+
await expect(resolvePlatformAssistantId()).resolves.toBe("");
|
|
168
|
+
expect(fetchCalls).toHaveLength(0);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("loads ids from validate when the in-memory override is empty", async () => {
|
|
172
|
+
process.env.ASSISTANT_API_KEY = "assistant-key";
|
|
173
|
+
setPlatformBaseUrl(BASE_URL);
|
|
174
|
+
fetchImpl = async () =>
|
|
175
|
+
new Response(
|
|
176
|
+
JSON.stringify({
|
|
177
|
+
assistant_id: ASSISTANT_ID,
|
|
178
|
+
organization_id: ORG_ID,
|
|
179
|
+
user_id: USER_ID,
|
|
180
|
+
}),
|
|
181
|
+
{ status: 200 },
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
await expect(resolvePlatformAssistantId()).resolves.toBe(ASSISTANT_ID);
|
|
185
|
+
expect(fetchCalls).toHaveLength(1);
|
|
186
|
+
expect(fetchCalls[0]?.url).toBe(
|
|
187
|
+
`${getPlatformBaseUrl().replace(/\/+$/, "")}${PLATFORM_IDENTITY_VALIDATE_PATH}`,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("does not retry validate during the cooldown after a failure", async () => {
|
|
192
|
+
process.env.ASSISTANT_API_KEY = "assistant-key";
|
|
193
|
+
setPlatformBaseUrl(BASE_URL);
|
|
194
|
+
fetchImpl = async () => new Response("down", { status: 503 });
|
|
195
|
+
|
|
196
|
+
await expect(resolvePlatformAssistantId()).resolves.toBe("");
|
|
197
|
+
await expect(resolvePlatformAssistantId()).resolves.toBe("");
|
|
198
|
+
expect(fetchCalls).toHaveLength(1);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("retries validate after a failed attempt once the cooldown is cleared", async () => {
|
|
202
|
+
process.env.ASSISTANT_API_KEY = "assistant-key";
|
|
203
|
+
setPlatformBaseUrl(BASE_URL);
|
|
204
|
+
fetchImpl = async () => new Response("down", { status: 503 });
|
|
205
|
+
|
|
206
|
+
await expect(resolvePlatformAssistantId()).resolves.toBe("");
|
|
207
|
+
_resetPlatformIdentityEnsureForTests();
|
|
208
|
+
fetchImpl = async () =>
|
|
209
|
+
new Response(
|
|
210
|
+
JSON.stringify({
|
|
211
|
+
assistant_id: ASSISTANT_ID,
|
|
212
|
+
organization_id: ORG_ID,
|
|
213
|
+
user_id: USER_ID,
|
|
214
|
+
}),
|
|
215
|
+
{ status: 200 },
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
await expect(resolvePlatformAssistantId()).resolves.toBe(ASSISTANT_ID);
|
|
219
|
+
expect(fetchCalls).toHaveLength(2);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("concurrent resolves share one in-flight validate", async () => {
|
|
223
|
+
process.env.ASSISTANT_API_KEY = "assistant-key";
|
|
224
|
+
setPlatformBaseUrl(BASE_URL);
|
|
225
|
+
let release!: (value: Response) => void;
|
|
226
|
+
let notifyFetchStarted!: () => void;
|
|
227
|
+
const fetchStarted = new Promise<void>((resolve) => {
|
|
228
|
+
notifyFetchStarted = resolve;
|
|
229
|
+
});
|
|
230
|
+
fetchImpl = () => {
|
|
231
|
+
notifyFetchStarted();
|
|
232
|
+
return new Promise<Response>((resolve) => {
|
|
233
|
+
release = resolve;
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const first = resolvePlatformAssistantId();
|
|
238
|
+
const second = resolvePlatformAssistantId();
|
|
239
|
+
await fetchStarted;
|
|
240
|
+
expect(fetchCalls).toHaveLength(1);
|
|
241
|
+
release(
|
|
242
|
+
new Response(
|
|
243
|
+
JSON.stringify({
|
|
244
|
+
assistant_id: ASSISTANT_ID,
|
|
245
|
+
organization_id: ORG_ID,
|
|
246
|
+
user_id: USER_ID,
|
|
247
|
+
}),
|
|
248
|
+
{ status: 200 },
|
|
249
|
+
),
|
|
250
|
+
);
|
|
251
|
+
await expect(first).resolves.toBe(ASSISTANT_ID);
|
|
252
|
+
await expect(second).resolves.toBe(ASSISTANT_ID);
|
|
253
|
+
expect(fetchCalls).toHaveLength(1);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
@@ -27,6 +27,8 @@ const {
|
|
|
27
27
|
setPlatformOrganizationId,
|
|
28
28
|
setPlatformUserId,
|
|
29
29
|
} = await import("../env.js");
|
|
30
|
+
const { _resetPlatformIdentityEnsureForTests } =
|
|
31
|
+
await import("../platform-identity.js");
|
|
30
32
|
const { rehydratePlatformCredentials } =
|
|
31
33
|
await import("../platform-rehydration.js");
|
|
32
34
|
|
|
@@ -39,23 +41,44 @@ describe("rehydratePlatformCredentials", () => {
|
|
|
39
41
|
const originalPlatformUrlEnv = process.env.VELLUM_PLATFORM_URL;
|
|
40
42
|
const originalOrgEnv = process.env.PLATFORM_ORGANIZATION_ID;
|
|
41
43
|
const originalUserEnv = process.env.PLATFORM_USER_ID;
|
|
44
|
+
const originalAssistantApiKeyEnv = process.env.ASSISTANT_API_KEY;
|
|
45
|
+
const originalFetch = globalThis.fetch;
|
|
46
|
+
const fetchCalls: Array<{ url: string; init?: RequestInit }> = [];
|
|
47
|
+
let fetchImpl: (
|
|
48
|
+
input: RequestInfo | URL,
|
|
49
|
+
init?: RequestInit,
|
|
50
|
+
) => Promise<Response> = async () =>
|
|
51
|
+
new Response("not found", { status: 404 });
|
|
42
52
|
|
|
43
53
|
beforeEach(() => {
|
|
44
54
|
mockSecureKeys = {};
|
|
45
55
|
throwForKey = undefined;
|
|
56
|
+
fetchCalls.length = 0;
|
|
57
|
+
fetchImpl = async () => new Response("not found", { status: 404 });
|
|
58
|
+
globalThis.fetch = (async (
|
|
59
|
+
input: RequestInfo | URL,
|
|
60
|
+
init?: RequestInit,
|
|
61
|
+
) => {
|
|
62
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
63
|
+
fetchCalls.push({ url, init });
|
|
64
|
+
return fetchImpl(input, init);
|
|
65
|
+
}) as unknown as typeof fetch;
|
|
46
66
|
// Env vars take precedence over the rehydrated overrides for some fields;
|
|
47
67
|
// clear them so the credential-store values are what we observe.
|
|
48
68
|
delete process.env.VELLUM_PLATFORM_URL;
|
|
49
69
|
delete process.env.PLATFORM_ORGANIZATION_ID;
|
|
50
70
|
delete process.env.PLATFORM_USER_ID;
|
|
71
|
+
delete process.env.ASSISTANT_API_KEY;
|
|
51
72
|
// Reset in-memory overrides between tests.
|
|
52
73
|
setPlatformBaseUrl(undefined);
|
|
53
74
|
setPlatformAssistantId(undefined);
|
|
54
75
|
setPlatformOrganizationId(undefined);
|
|
55
76
|
setPlatformUserId(undefined);
|
|
77
|
+
_resetPlatformIdentityEnsureForTests();
|
|
56
78
|
});
|
|
57
79
|
|
|
58
80
|
afterEach(() => {
|
|
81
|
+
globalThis.fetch = originalFetch;
|
|
59
82
|
if (originalPlatformUrlEnv === undefined) {
|
|
60
83
|
delete process.env.VELLUM_PLATFORM_URL;
|
|
61
84
|
} else {
|
|
@@ -71,13 +94,18 @@ describe("rehydratePlatformCredentials", () => {
|
|
|
71
94
|
} else {
|
|
72
95
|
process.env.PLATFORM_USER_ID = originalUserEnv;
|
|
73
96
|
}
|
|
97
|
+
if (originalAssistantApiKeyEnv === undefined) {
|
|
98
|
+
delete process.env.ASSISTANT_API_KEY;
|
|
99
|
+
} else {
|
|
100
|
+
process.env.ASSISTANT_API_KEY = originalAssistantApiKeyEnv;
|
|
101
|
+
}
|
|
74
102
|
setPlatformBaseUrl(undefined);
|
|
75
103
|
setPlatformAssistantId(undefined);
|
|
76
104
|
setPlatformOrganizationId(undefined);
|
|
77
105
|
setPlatformUserId(undefined);
|
|
78
106
|
});
|
|
79
107
|
|
|
80
|
-
test("rehydrates base URL
|
|
108
|
+
test("rehydrates the platform base URL from the credential store", async () => {
|
|
81
109
|
mockSecureKeys[credentialKey("vellum", "platform_base_url")] = PROD_URL;
|
|
82
110
|
mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
|
|
83
111
|
ASSISTANT_ID;
|
|
@@ -88,21 +116,18 @@ describe("rehydratePlatformCredentials", () => {
|
|
|
88
116
|
await rehydratePlatformCredentials();
|
|
89
117
|
|
|
90
118
|
expect(getPlatformBaseUrl()).toBe(PROD_URL);
|
|
91
|
-
expect(getPlatformAssistantId()).toBe(
|
|
92
|
-
expect(getPlatformOrganizationId()).toBe(
|
|
93
|
-
expect(getPlatformUserId()).toBe(
|
|
119
|
+
expect(getPlatformAssistantId()).toBe("");
|
|
120
|
+
expect(getPlatformOrganizationId()).toBe("");
|
|
121
|
+
expect(getPlatformUserId()).toBe("");
|
|
94
122
|
});
|
|
95
123
|
|
|
96
124
|
test("trims stored values before applying them", async () => {
|
|
97
125
|
mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
|
|
98
126
|
` ${PROD_URL} `;
|
|
99
|
-
mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
|
|
100
|
-
`\n${ASSISTANT_ID}\n`;
|
|
101
127
|
|
|
102
128
|
await rehydratePlatformCredentials();
|
|
103
129
|
|
|
104
130
|
expect(getPlatformBaseUrl()).toBe(PROD_URL);
|
|
105
|
-
expect(getPlatformAssistantId()).toBe(ASSISTANT_ID);
|
|
106
131
|
});
|
|
107
132
|
|
|
108
133
|
test("leaves overrides untouched when nothing is stored", async () => {
|
|
@@ -113,13 +138,78 @@ describe("rehydratePlatformCredentials", () => {
|
|
|
113
138
|
expect(getPlatformUserId()).toBe("");
|
|
114
139
|
});
|
|
115
140
|
|
|
116
|
-
test("a
|
|
141
|
+
test("a credential-store read failure does not throw", async () => {
|
|
117
142
|
throwForKey = credentialKey("vellum", "platform_base_url");
|
|
143
|
+
|
|
144
|
+
await expect(rehydratePlatformCredentials()).resolves.toBeUndefined();
|
|
145
|
+
expect(getPlatformAssistantId()).toBe("");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("trades the assistant API key for ids via platform validate", async () => {
|
|
149
|
+
mockSecureKeys[credentialKey("vellum", "platform_base_url")] = PROD_URL;
|
|
150
|
+
mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
|
|
151
|
+
"assistant-key";
|
|
152
|
+
fetchImpl = async () =>
|
|
153
|
+
new Response(
|
|
154
|
+
JSON.stringify({
|
|
155
|
+
assistant_id: ASSISTANT_ID,
|
|
156
|
+
organization_id: ORG_ID,
|
|
157
|
+
user_id: USER_ID,
|
|
158
|
+
}),
|
|
159
|
+
{ status: 200 },
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await rehydratePlatformCredentials();
|
|
163
|
+
|
|
164
|
+
expect(fetchCalls).toHaveLength(1);
|
|
165
|
+
expect(fetchCalls[0]?.url).toBe(
|
|
166
|
+
`${PROD_URL}/v1/internal/assistants/validate/`,
|
|
167
|
+
);
|
|
168
|
+
const headers = new Headers(fetchCalls[0]?.init?.headers);
|
|
169
|
+
expect(headers.get("Authorization")).toBe("Api-Key assistant-key");
|
|
170
|
+
expect(getPlatformAssistantId()).toBe(ASSISTANT_ID);
|
|
171
|
+
expect(getPlatformOrganizationId()).toBe(ORG_ID);
|
|
172
|
+
expect(getPlatformUserId()).toBe(USER_ID);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("validate ids are used when the vault also has a copy", async () => {
|
|
176
|
+
mockSecureKeys[credentialKey("vellum", "platform_base_url")] = PROD_URL;
|
|
177
|
+
mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
|
|
178
|
+
"assistant-key";
|
|
118
179
|
mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
|
|
119
|
-
|
|
180
|
+
"asst-from-vault";
|
|
181
|
+
fetchImpl = async () =>
|
|
182
|
+
new Response(
|
|
183
|
+
JSON.stringify({
|
|
184
|
+
assistant_id: ASSISTANT_ID,
|
|
185
|
+
organization_id: ORG_ID,
|
|
186
|
+
user_id: USER_ID,
|
|
187
|
+
}),
|
|
188
|
+
{ status: 200 },
|
|
189
|
+
);
|
|
120
190
|
|
|
121
191
|
await rehydratePlatformCredentials();
|
|
122
192
|
|
|
123
193
|
expect(getPlatformAssistantId()).toBe(ASSISTANT_ID);
|
|
124
194
|
});
|
|
195
|
+
|
|
196
|
+
test("does not populate ids from the vault when validate is unreachable", async () => {
|
|
197
|
+
mockSecureKeys[credentialKey("vellum", "platform_base_url")] = PROD_URL;
|
|
198
|
+
mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
|
|
199
|
+
"assistant-key";
|
|
200
|
+
mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
|
|
201
|
+
ASSISTANT_ID;
|
|
202
|
+
mockSecureKeys[credentialKey("vellum", "platform_organization_id")] =
|
|
203
|
+
ORG_ID;
|
|
204
|
+
mockSecureKeys[credentialKey("vellum", "platform_user_id")] = USER_ID;
|
|
205
|
+
fetchImpl = async () => {
|
|
206
|
+
throw new Error("network down");
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
await rehydratePlatformCredentials();
|
|
210
|
+
|
|
211
|
+
expect(getPlatformAssistantId()).toBe("");
|
|
212
|
+
expect(getPlatformOrganizationId()).toBe("");
|
|
213
|
+
expect(getPlatformUserId()).toBe("");
|
|
214
|
+
});
|
|
125
215
|
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw-config helper shared by the config PATCH and SET routes, the paths that
|
|
3
|
+
* write a coding agent's entry.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isPlainObject } from "../util/object.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Clearing a leaf of a coding agent's entry is a write of `null` - a PATCH of
|
|
10
|
+
* `{ acp: { agents: { <id>: { model: null } } } }` or a
|
|
11
|
+
* `config set acp.agents.<id>.model null`. The deep-merge assigns that literal
|
|
12
|
+
* `null` because the stored value is a scalar, and a SET writes it verbatim by
|
|
13
|
+
* design. Both `command` and `model` are optional strings in `AcpConfigSchema`,
|
|
14
|
+
* so a persisted `null` makes every later `loadConfig()` warn and take its
|
|
15
|
+
* salvage path, dropping the whole `acp` section with the agents defined in it.
|
|
16
|
+
* Clearing means removing the key, so drop it here on every write path.
|
|
17
|
+
*
|
|
18
|
+
* A cleared `command` then reads as an omitted one: a bundled id inherits the
|
|
19
|
+
* profile's command, and an id that has no profile fails the agents refinement
|
|
20
|
+
* with a message naming the id, which tells the user more than zod's "expected
|
|
21
|
+
* string, received null".
|
|
22
|
+
*/
|
|
23
|
+
export function scrubNulledAcpAgentLeaves(raw: Record<string, unknown>): void {
|
|
24
|
+
const acp = raw.acp;
|
|
25
|
+
if (!isPlainObject(acp)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const agents = acp.agents;
|
|
29
|
+
if (!isPlainObject(agents)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const entry of Object.values(agents)) {
|
|
33
|
+
if (!isPlainObject(entry)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (entry.command === null) {
|
|
37
|
+
delete entry.command;
|
|
38
|
+
}
|
|
39
|
+
if (entry.model === null) {
|
|
40
|
+
delete entry.model;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -13,15 +13,17 @@ describe("DEFAULT_ACP_AGENT_PROFILES", () => {
|
|
|
13
13
|
]);
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
test("claude profile uses the @agentclientprotocol adapter binary", () => {
|
|
16
|
+
test("claude profile uses the @agentclientprotocol adapter binary on opus", () => {
|
|
17
17
|
expect(DEFAULT_ACP_AGENT_PROFILES.claude).toEqual({
|
|
18
18
|
command: "claude-agent-acp",
|
|
19
19
|
args: [],
|
|
20
20
|
description: "Claude Code (via @agentclientprotocol/claude-agent-acp)",
|
|
21
|
+
model: "opus",
|
|
21
22
|
});
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
test("codex profile uses the @agentclientprotocol adapter binary", () => {
|
|
26
|
+
// No model: Codex aliases are its own, so the adapter's default stands.
|
|
25
27
|
expect(DEFAULT_ACP_AGENT_PROFILES.codex).toEqual({
|
|
26
28
|
command: "codex-acp",
|
|
27
29
|
args: [],
|
|
@@ -44,11 +46,19 @@ describe("DEFAULT_ACP_AGENT_PROFILES", () => {
|
|
|
44
46
|
describe("DEFAULT_AGENT_NPM_PACKAGES", () => {
|
|
45
47
|
test("is keyed by command name with the canonical npm package", () => {
|
|
46
48
|
expect(DEFAULT_AGENT_NPM_PACKAGES).toEqual({
|
|
47
|
-
"claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
|
|
48
|
-
"codex-acp": "@agentclientprotocol/codex-acp",
|
|
49
|
+
"claude-agent-acp": "@agentclientprotocol/claude-agent-acp@0.75.1",
|
|
50
|
+
"codex-acp": "@agentclientprotocol/codex-acp@1.10.0",
|
|
49
51
|
});
|
|
50
52
|
});
|
|
51
53
|
|
|
54
|
+
test("every package spec pins an exact version", () => {
|
|
55
|
+
// A bare name would let a clean install resolve whatever npm calls
|
|
56
|
+
// latest, which is what the repo's pinning rule exists to prevent.
|
|
57
|
+
for (const spec of Object.values(DEFAULT_AGENT_NPM_PACKAGES)) {
|
|
58
|
+
expect(spec).toMatch(/^@?[^@]+@\d+\.\d+\.\d+$/);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
52
62
|
test("every default profile's command has a matching npm package", () => {
|
|
53
63
|
for (const profile of Object.values(DEFAULT_ACP_AGENT_PROFILES)) {
|
|
54
64
|
expect(DEFAULT_AGENT_NPM_PACKAGES[profile.command]).toBeDefined();
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { AcpAgentConfig } from "./acp-schema.js";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A bundled profile always names its command; a user entry for the same id
|
|
5
|
+
* may omit `command` and inherit this one.
|
|
6
|
+
*/
|
|
7
|
+
export type AcpAgentProfile = AcpAgentConfig & { command: string };
|
|
8
|
+
|
|
3
9
|
// Shared frozen empty args array — these defaults are read by every ACP spawn,
|
|
4
10
|
// so a single accidental `args.push(...)` from one caller would corrupt every
|
|
5
11
|
// subsequent read. Cast through `unknown` because `AcpAgentConfig.args` is
|
|
@@ -13,17 +19,22 @@ const FROZEN_EMPTY_ARGS = Object.freeze([] as string[]) as unknown as string[];
|
|
|
13
19
|
* falls back to this map so common agents like `claude` and `codex` Just Work
|
|
14
20
|
* without requiring per-user config.
|
|
15
21
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
22
|
+
* A profile's `model` is the model a session of that agent starts on when the
|
|
23
|
+
* ask names none, and is the rung `acp.agents.<id>.model` fills: a user config
|
|
24
|
+
* entry that names a model overrides it, one that omits it inherits it.
|
|
25
|
+
*
|
|
26
|
+
* Keyed by agent id. Deeply frozen: the outer object, each profile, and the
|
|
27
|
+
* `args` arrays, so mutation throws in strict mode rather than silently
|
|
18
28
|
* corrupting the shared defaults.
|
|
19
29
|
*/
|
|
20
30
|
export const DEFAULT_ACP_AGENT_PROFILES: Readonly<
|
|
21
|
-
Record<string,
|
|
31
|
+
Record<string, AcpAgentProfile>
|
|
22
32
|
> = Object.freeze({
|
|
23
33
|
claude: Object.freeze({
|
|
24
34
|
command: "claude-agent-acp",
|
|
25
35
|
args: FROZEN_EMPTY_ARGS,
|
|
26
36
|
description: "Claude Code (via @agentclientprotocol/claude-agent-acp)",
|
|
37
|
+
model: "opus",
|
|
27
38
|
}),
|
|
28
39
|
codex: Object.freeze({
|
|
29
40
|
command: "codex-acp",
|
|
@@ -33,15 +44,21 @@ export const DEFAULT_ACP_AGENT_PROFILES: Readonly<
|
|
|
33
44
|
});
|
|
34
45
|
|
|
35
46
|
/**
|
|
36
|
-
* Single source of truth for adapter binary → npm package
|
|
47
|
+
* Single source of truth for adapter binary → npm package spec. Automatic
|
|
37
48
|
* installation and the resolver's install hints use this map, so a new
|
|
38
49
|
* adapter only needs one entry here.
|
|
39
50
|
*
|
|
40
51
|
* Keyed by command name (not agent id) so the mapping follows the binary
|
|
41
52
|
* regardless of how a user's config aliases an agent.
|
|
53
|
+
*
|
|
54
|
+
* Values carry an exact version, so a clean install lands the adapter version
|
|
55
|
+
* the daemon was tested against instead of whatever npm calls latest today.
|
|
56
|
+
* An adapter already on PATH is left alone: installation runs only when
|
|
57
|
+
* preflight found no binary at all, so this never moves a version the user
|
|
58
|
+
* installed themselves.
|
|
42
59
|
*/
|
|
43
60
|
export const DEFAULT_AGENT_NPM_PACKAGES: Readonly<Record<string, string>> =
|
|
44
61
|
Object.freeze({
|
|
45
|
-
"claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
|
|
46
|
-
"codex-acp": "@agentclientprotocol/codex-acp",
|
|
62
|
+
"claude-agent-acp": "@agentclientprotocol/claude-agent-acp@0.75.1",
|
|
63
|
+
"codex-acp": "@agentclientprotocol/codex-acp@1.10.0",
|
|
47
64
|
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Covers the ACP config leaf that carries model selection, the per-agent
|
|
3
|
+
* `model`. It is optional with no `.default()`, so an absent value must stay
|
|
4
|
+
* absent after parsing: the bundled profile supplies the model a session
|
|
5
|
+
* starts on, and a key materialized as `undefined` would shadow it.
|
|
6
|
+
*
|
|
7
|
+
* `command` is optional in the same shape, so an entry for a bundled id can
|
|
8
|
+
* carry a model and nothing else; the record's own check keeps a command
|
|
9
|
+
* required for every id that has no bundled profile to inherit one from.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, expect, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
import { AcpAgentConfigSchema, AcpConfigSchema } from "./acp-schema.js";
|
|
15
|
+
|
|
16
|
+
describe("AcpConfigSchema", () => {
|
|
17
|
+
test("populates defaults for an empty config", () => {
|
|
18
|
+
expect(AcpConfigSchema.parse({})).toEqual({
|
|
19
|
+
maxConcurrentSessions: 4,
|
|
20
|
+
agents: {},
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("AcpConfigSchema agents", () => {
|
|
26
|
+
test("a bundled id's entry may omit command and name only a model", () => {
|
|
27
|
+
const parsed = AcpConfigSchema.parse({
|
|
28
|
+
agents: { claude: { model: "sonnet" } },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(parsed.agents).toEqual({ claude: { args: [], model: "sonnet" } });
|
|
32
|
+
expect("command" in parsed.agents.claude!).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("an id with no bundled profile needs a command", () => {
|
|
36
|
+
const result = AcpConfigSchema.safeParse({
|
|
37
|
+
agents: { mine: { model: "sonnet" } },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(result.success).toBe(false);
|
|
41
|
+
if (result.success) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
expect(result.error.issues.map((issue) => issue.path)).toEqual([
|
|
45
|
+
["agents", "mine", "command"],
|
|
46
|
+
]);
|
|
47
|
+
expect(result.error.issues[0]?.message).toContain("acp.agents.mine");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("an id naming an Object.prototype member inherits no command", () => {
|
|
51
|
+
expect(
|
|
52
|
+
AcpConfigSchema.safeParse({ agents: { constructor: { model: "x" } } })
|
|
53
|
+
.success,
|
|
54
|
+
).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("AcpAgentConfigSchema", () => {
|
|
59
|
+
test("round-trips a per-agent model override", () => {
|
|
60
|
+
expect(
|
|
61
|
+
AcpAgentConfigSchema.parse({
|
|
62
|
+
command: "claude-agent-acp",
|
|
63
|
+
model: "opus",
|
|
64
|
+
}),
|
|
65
|
+
).toEqual({ command: "claude-agent-acp", args: [], model: "opus" });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("leaves model unset when omitted, so a bundled profile's shows through", () => {
|
|
69
|
+
const parsed = AcpAgentConfigSchema.parse({ command: "claude-agent-acp" });
|
|
70
|
+
|
|
71
|
+
expect(parsed.model).toBeUndefined();
|
|
72
|
+
expect("model" in parsed).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("rejects a non-string model", () => {
|
|
76
|
+
expect(
|
|
77
|
+
AcpAgentConfigSchema.safeParse({ command: "claude-agent-acp", model: 3 })
|
|
78
|
+
.success,
|
|
79
|
+
).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|