@vellumai/assistant 0.12.1 → 0.12.2-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/ARCHITECTURE.md +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/openapi.yaml +327 -52
- package/package.json +3 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +19 -7
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +212 -3
- package/src/__tests__/credential-security-invariants.test.ts +4 -2
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/heartbeat-service.test.ts +56 -40
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-cu-proxy.test.ts +539 -0
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/oauth-commands-routes.test.ts +113 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/service-account-token.test.ts +247 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/unicode.test.ts +13 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +96 -10
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/agent/loop.ts +1 -0
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/events/notification-intent.ts +2 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/avatar/identity-avatar.ts +7 -2
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +53 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +59 -46
- package/src/calls/voice-triage-escalate.ts +175 -56
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +24 -12
- package/src/config/loader.ts +1 -0
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/services.ts +8 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/credential-health/credential-health-service.ts +11 -17
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-runtime-assembly.ts +2 -1
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +15 -9
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-cu-proxy.ts +186 -19
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/reaction-history-render.ts +2 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +122 -0
- package/src/desktop/desktop-chrome-policy.test.ts +79 -0
- package/src/desktop/desktop-chrome-policy.ts +47 -0
- package/src/desktop/desktop-chrome-session.ts +37 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +308 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +730 -0
- package/src/desktop/desktop-session-manager.ts +850 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-drag.ts +177 -0
- package/src/desktop/desktop-window-manager-config.test.ts +230 -0
- package/src/desktop/desktop-window-manager-config.ts +238 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/heartbeat/heartbeat-service.ts +21 -3
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +213 -693
- package/src/live-voice/protocol.ts +0 -2
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
- package/src/oauth/byo-connection.test.ts +105 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +73 -0
- package/src/oauth/connection-resolver.ts +95 -2
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +204 -25
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-queries.ts +2 -2
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +4 -2
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/host-utils.ts +10 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +28 -2
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/__tests__/retry-callsite.test.ts +41 -0
- package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/endpoint-probe.ts +10 -1
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +212 -79
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/providers/opencode/client.test.ts +73 -4
- package/src/providers/opencode/client.ts +60 -3
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +30 -19
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +3 -1
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/host-observe.test.ts +40 -1
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/host-observe.ts +11 -1
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +73 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/conversation-routes.ts +16 -3
- package/src/runtime/routes/credential-routes.ts +19 -10
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +61 -4
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/index.ts +4 -2
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +6 -20
- package/src/runtime/routes/oauth-commands-routes.ts +36 -0
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/security/untrusted-content.ts +3 -1
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +121 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/filesystem/search.ts +2 -1
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/network/web-search.ts +6 -1
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/types/host-capabilities.ts +5 -3
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/util/unicode.ts +15 -1
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +6 -55
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -194,7 +194,10 @@ describe("AcpAgentProcess.createSession auth_required handling", () => {
|
|
|
194
194
|
},
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
-
await expect(proc.createSession("/tmp")).resolves.
|
|
197
|
+
await expect(proc.createSession("/tmp")).resolves.toEqual({
|
|
198
|
+
sessionId: "session-1",
|
|
199
|
+
configOptions: [],
|
|
200
|
+
});
|
|
198
201
|
expect(authenticateCalls()).toBe(1);
|
|
199
202
|
});
|
|
200
203
|
|
package/src/acp/agent-process.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
9
|
+
import { basename } from "node:path";
|
|
9
10
|
import { Readable, Writable } from "node:stream";
|
|
10
11
|
|
|
11
12
|
import type {
|
|
@@ -14,14 +15,25 @@ import type {
|
|
|
14
15
|
AuthMethodEnvVar,
|
|
15
16
|
Client,
|
|
16
17
|
InitializeResponse,
|
|
18
|
+
LoadSessionResponse,
|
|
17
19
|
NewSessionResponse,
|
|
18
20
|
PromptResponse,
|
|
21
|
+
ResumeSessionResponse,
|
|
22
|
+
SessionConfigOption,
|
|
23
|
+
SetSessionConfigOptionRequest,
|
|
19
24
|
} from "@agentclientprotocol/sdk";
|
|
20
25
|
import * as acp from "@agentclientprotocol/sdk";
|
|
21
26
|
|
|
22
27
|
import { getLogger } from "../util/logger.js";
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
28
|
+
import { safeStringSlice } from "../util/unicode.js";
|
|
29
|
+
import {
|
|
30
|
+
AcpAuthRequiredError,
|
|
31
|
+
CLAUDE_ACP_COMMAND,
|
|
32
|
+
isAcpAuthRequired,
|
|
33
|
+
isClaudeAuthFailureMessage,
|
|
34
|
+
requestErrorReason,
|
|
35
|
+
} from "./auth-required.js";
|
|
36
|
+
import { type AcpAgentConfig, AcpConfigOptionRefusedError } from "./types.js";
|
|
25
37
|
|
|
26
38
|
const log = getLogger("acp");
|
|
27
39
|
|
|
@@ -32,6 +44,16 @@ const log = getLogger("acp");
|
|
|
32
44
|
*/
|
|
33
45
|
const STDERR_RETENTION_BYTES = 4096;
|
|
34
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Normalizes the SDK's optional-and-nullable config-option field to an array,
|
|
49
|
+
* so every session call hands its caller the same shape.
|
|
50
|
+
*/
|
|
51
|
+
function normalizeConfigOptions(
|
|
52
|
+
configOptions: SessionConfigOption[] | null | undefined,
|
|
53
|
+
): SessionConfigOption[] {
|
|
54
|
+
return configOptions ?? [];
|
|
55
|
+
}
|
|
56
|
+
|
|
35
57
|
function isEnvVarMethod(
|
|
36
58
|
method: AuthMethod,
|
|
37
59
|
): method is AuthMethodEnvVar & { type: "env_var" } {
|
|
@@ -142,7 +164,7 @@ export class AcpAgentProcess {
|
|
|
142
164
|
// deriveFailureError reads from the end, so dropping the head is lossless.
|
|
143
165
|
const line =
|
|
144
166
|
text.length > STDERR_RETENTION_BYTES
|
|
145
|
-
? text.
|
|
167
|
+
? safeStringSlice(text, text.length - STDERR_RETENTION_BYTES)
|
|
146
168
|
: text;
|
|
147
169
|
this.stderrSeq += 1;
|
|
148
170
|
this.stderrRing.push({ seq: this.stderrSeq, text: line });
|
|
@@ -355,16 +377,21 @@ export class AcpAgentProcess {
|
|
|
355
377
|
|
|
356
378
|
/**
|
|
357
379
|
* Creates a new ACP session in the specified working directory.
|
|
358
|
-
* Returns the session ID.
|
|
380
|
+
* Returns the session ID and the config options the agent reported.
|
|
359
381
|
*/
|
|
360
|
-
async createSession(
|
|
382
|
+
async createSession(
|
|
383
|
+
cwd: string,
|
|
384
|
+
): Promise<{ sessionId: string; configOptions: SessionConfigOption[] }> {
|
|
361
385
|
log.info({ agentId: this.agentId, cwd }, "Creating ACP session");
|
|
362
386
|
|
|
363
387
|
const result: NewSessionResponse = await this.withAuthRetry(() =>
|
|
364
388
|
this.requireConnection().newSession({ cwd, mcpServers: [] }),
|
|
365
389
|
);
|
|
366
390
|
|
|
367
|
-
return
|
|
391
|
+
return {
|
|
392
|
+
sessionId: result.sessionId,
|
|
393
|
+
configOptions: normalizeConfigOptions(result.configOptions),
|
|
394
|
+
};
|
|
368
395
|
}
|
|
369
396
|
|
|
370
397
|
/**
|
|
@@ -375,12 +402,17 @@ export class AcpAgentProcess {
|
|
|
375
402
|
* callers should suppress forwarding of those replayed updates (see
|
|
376
403
|
* VellumAcpClientHandler.beginReplaySuppression).
|
|
377
404
|
*/
|
|
378
|
-
async loadSession(
|
|
405
|
+
async loadSession(
|
|
406
|
+
sessionId: string,
|
|
407
|
+
cwd: string,
|
|
408
|
+
): Promise<{ configOptions: SessionConfigOption[] }> {
|
|
379
409
|
log.info({ agentId: this.agentId, sessionId, cwd }, "Loading ACP session");
|
|
380
410
|
|
|
381
|
-
await this.withAuthRetry(() =>
|
|
411
|
+
const result: LoadSessionResponse = await this.withAuthRetry(() =>
|
|
382
412
|
this.requireConnection().loadSession({ sessionId, cwd, mcpServers: [] }),
|
|
383
413
|
);
|
|
414
|
+
|
|
415
|
+
return { configOptions: normalizeConfigOptions(result.configOptions) };
|
|
384
416
|
}
|
|
385
417
|
|
|
386
418
|
/**
|
|
@@ -390,16 +422,70 @@ export class AcpAgentProcess {
|
|
|
390
422
|
* preferred when the agent advertises the capability
|
|
391
423
|
* (see supportsSessionResume).
|
|
392
424
|
*/
|
|
393
|
-
async resumeSession(
|
|
425
|
+
async resumeSession(
|
|
426
|
+
sessionId: string,
|
|
427
|
+
cwd: string,
|
|
428
|
+
): Promise<{ configOptions: SessionConfigOption[] }> {
|
|
394
429
|
log.info({ agentId: this.agentId, sessionId, cwd }, "Resuming ACP session");
|
|
395
430
|
|
|
396
|
-
await this.withAuthRetry(() =>
|
|
431
|
+
const result: ResumeSessionResponse = await this.withAuthRetry(() =>
|
|
397
432
|
this.requireConnection().resumeSession({
|
|
398
433
|
sessionId,
|
|
399
434
|
cwd,
|
|
400
435
|
mcpServers: [],
|
|
401
436
|
}),
|
|
402
437
|
);
|
|
438
|
+
|
|
439
|
+
return { configOptions: normalizeConfigOptions(result.configOptions) };
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Sets one session config option (e.g. the model selector) via
|
|
444
|
+
* `session/set_config_option`. The agent answers with the full refreshed
|
|
445
|
+
* option set, which is returned to the caller.
|
|
446
|
+
*
|
|
447
|
+
* A boolean value sends the `type: "boolean"` request variant; a string
|
|
448
|
+
* sends the value-id variant.
|
|
449
|
+
*/
|
|
450
|
+
async setConfigOption(
|
|
451
|
+
sessionId: string,
|
|
452
|
+
configId: string,
|
|
453
|
+
value: string | boolean,
|
|
454
|
+
): Promise<SessionConfigOption[]> {
|
|
455
|
+
log.info(
|
|
456
|
+
{ agentId: this.agentId, sessionId, configId, value },
|
|
457
|
+
"Setting ACP session config option",
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
const request: SetSessionConfigOptionRequest =
|
|
461
|
+
typeof value === "boolean"
|
|
462
|
+
? { sessionId, configId, type: "boolean", value }
|
|
463
|
+
: { sessionId, configId, value };
|
|
464
|
+
|
|
465
|
+
const response = await this.withAuthRetry(async () => {
|
|
466
|
+
try {
|
|
467
|
+
return await this.requireConnection().setSessionConfigOption(request);
|
|
468
|
+
} catch (err) {
|
|
469
|
+
// Wrapped here, at the request itself, so `withAuthRetry` still sees
|
|
470
|
+
// an auth_required answer and a caller cannot confuse the adapter's
|
|
471
|
+
// refusal with a transport or authentication failure. Claude's
|
|
472
|
+
// expired-token failure travels as a generic error whose reason is
|
|
473
|
+
// the only signal, so under the Claude adapter it is left for the
|
|
474
|
+
// caller to classify.
|
|
475
|
+
if (err instanceof acp.RequestError && !isAcpAuthRequired(err)) {
|
|
476
|
+
const reason = requestErrorReason(err);
|
|
477
|
+
const claudeAuthFailure =
|
|
478
|
+
basename(this.config.command) === CLAUDE_ACP_COMMAND &&
|
|
479
|
+
isClaudeAuthFailureMessage(reason);
|
|
480
|
+
if (!claudeAuthFailure) {
|
|
481
|
+
throw new AcpConfigOptionRefusedError(reason);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
throw err;
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
return normalizeConfigOptions(response.configOptions);
|
|
403
489
|
}
|
|
404
490
|
|
|
405
491
|
/**
|
package/src/acp/auth-required.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* 2. Credentials present but rejected (the expired/revoked case): the CLI
|
|
9
9
|
* surfaces the API 401 as an error message it authors, and the adapter
|
|
10
10
|
* relays it as a generic -32603 internal error, NOT as `auth_required`.
|
|
11
|
-
* {@link isClaudeAuthFailureMessage}
|
|
11
|
+
* {@link isClaudeAuthFailureMessage}, over the text
|
|
12
|
+
* {@link requestErrorReason} decodes out of the rejection.
|
|
12
13
|
*
|
|
13
14
|
* A positive detection becomes {@link ACP_CLAUDE_AUTH_REQUIRED_CODE} on the
|
|
14
15
|
* `acp_auth_required` event, which the client turns into the inline
|
|
@@ -81,6 +82,54 @@ export function isClaudeAuthFailureMessage(
|
|
|
81
82
|
);
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
/** Messages the SDK's `RequestError` factories build when given no text. */
|
|
86
|
+
const GENERIC_RPC_MESSAGES: ReadonlySet<string> = new Set([
|
|
87
|
+
"Parse error",
|
|
88
|
+
"Invalid request",
|
|
89
|
+
"Invalid params",
|
|
90
|
+
"Internal error",
|
|
91
|
+
"Request cancelled",
|
|
92
|
+
"Authentication required",
|
|
93
|
+
"Resource not found",
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The sentence behind a JSON-RPC rejection. An adapter that throws a plain
|
|
98
|
+
* Error reaches the client as a bare "Internal error" whose real text the
|
|
99
|
+
* agent-side SDK moved into `data.details`. An adapter that raises
|
|
100
|
+
* `RequestError.internalError(data, text)` keeps its words in the message and
|
|
101
|
+
* only supplemental context in `data`, so a specific message wins. Behind a
|
|
102
|
+
* generic message such as "Invalid params", `data.details` or the serialized
|
|
103
|
+
* payload supplies the reason instead. Duck-typed like
|
|
104
|
+
* {@link isAcpAuthRequired}:
|
|
105
|
+
* `message` is read as a property, so a rejection that arrives as a plain
|
|
106
|
+
* object off the wire decodes the same as an `Error` instance.
|
|
107
|
+
*/
|
|
108
|
+
export function requestErrorReason(err: unknown): string {
|
|
109
|
+
if (typeof err !== "object" || err === null) {
|
|
110
|
+
return String(err);
|
|
111
|
+
}
|
|
112
|
+
const { data, message } = err as { data?: unknown; message?: unknown };
|
|
113
|
+
const own = typeof message === "string" ? message : String(err);
|
|
114
|
+
if (data == null) {
|
|
115
|
+
return own;
|
|
116
|
+
}
|
|
117
|
+
if (
|
|
118
|
+
typeof message === "string" &&
|
|
119
|
+
message.length > 0 &&
|
|
120
|
+
!GENERIC_RPC_MESSAGES.has(message)
|
|
121
|
+
) {
|
|
122
|
+
return message;
|
|
123
|
+
}
|
|
124
|
+
const details = (data as { details?: unknown }).details;
|
|
125
|
+
if (typeof details === "string" && details.length > 0) {
|
|
126
|
+
return details;
|
|
127
|
+
}
|
|
128
|
+
// JSON.stringify answers undefined for a value it cannot represent.
|
|
129
|
+
const serialized: string | undefined = JSON.stringify(data);
|
|
130
|
+
return serialized ?? own;
|
|
131
|
+
}
|
|
132
|
+
|
|
84
133
|
/** The adapter whose auth failures the Connect Claude flow can repair. */
|
|
85
134
|
export const CLAUDE_ACP_COMMAND = "claude-agent-acp";
|
|
86
135
|
|
|
@@ -78,7 +78,7 @@ describe("ensureAdapterInstalled", () => {
|
|
|
78
78
|
expect(args).toEqual([
|
|
79
79
|
"add",
|
|
80
80
|
"--global",
|
|
81
|
-
"@agentclientprotocol/claude-agent-acp",
|
|
81
|
+
"@agentclientprotocol/claude-agent-acp@0.75.1",
|
|
82
82
|
]);
|
|
83
83
|
});
|
|
84
84
|
|
|
@@ -198,8 +198,8 @@ describe("ensureAdapterInstalled", () => {
|
|
|
198
198
|
(call) => (call[1] as string[])[2],
|
|
199
199
|
);
|
|
200
200
|
expect(installedPackages.sort()).toEqual([
|
|
201
|
-
"@agentclientprotocol/claude-agent-acp",
|
|
202
|
-
"@agentclientprotocol/codex-acp",
|
|
201
|
+
"@agentclientprotocol/claude-agent-acp@0.75.1",
|
|
202
|
+
"@agentclientprotocol/codex-acp@1.10.0",
|
|
203
203
|
]);
|
|
204
204
|
});
|
|
205
205
|
});
|
|
@@ -232,7 +232,7 @@ describe("resolveAgentWithAutoInstall - resolution order", () => {
|
|
|
232
232
|
// The resolved command is the REAL binary, not a `bun x` wrapper.
|
|
233
233
|
expect(result.resolved.agent.command).toBe("claude-agent-acp");
|
|
234
234
|
expect(result.autoInstalledPackage).toBe(
|
|
235
|
-
"@agentclientprotocol/claude-agent-acp",
|
|
235
|
+
"@agentclientprotocol/claude-agent-acp@0.75.1",
|
|
236
236
|
);
|
|
237
237
|
expect(result.failureMessage).toBeUndefined();
|
|
238
238
|
expect(execFileMock).toHaveBeenCalledTimes(1);
|
|
@@ -264,12 +264,14 @@ describe("resolveAgentWithAutoInstall - resolution order", () => {
|
|
|
264
264
|
return;
|
|
265
265
|
}
|
|
266
266
|
expect(result.resolved.agent.command).toBe("codex-acp");
|
|
267
|
-
expect(result.autoInstalledPackage).toBe(
|
|
267
|
+
expect(result.autoInstalledPackage).toBe(
|
|
268
|
+
"@agentclientprotocol/codex-acp@1.10.0",
|
|
269
|
+
);
|
|
268
270
|
expect(execFileMock).toHaveBeenCalledTimes(1);
|
|
269
271
|
expect(execFileMock.mock.calls[0][1]).toEqual([
|
|
270
272
|
"add",
|
|
271
273
|
"--global",
|
|
272
|
-
"@agentclientprotocol/codex-acp",
|
|
274
|
+
"@agentclientprotocol/codex-acp@1.10.0",
|
|
273
275
|
]);
|
|
274
276
|
});
|
|
275
277
|
|
|
@@ -295,7 +297,7 @@ describe("resolveAgentWithAutoInstall - resolution order", () => {
|
|
|
295
297
|
expect(result.resolved.ok).toBe(false);
|
|
296
298
|
expect(result.failureMessage).toContain("claude-agent-acp is not on PATH");
|
|
297
299
|
expect(result.failureMessage).toContain(
|
|
298
|
-
"bun add -g @agentclientprotocol/claude-agent-acp",
|
|
300
|
+
"bun add -g @agentclientprotocol/claude-agent-acp@0.75.1",
|
|
299
301
|
);
|
|
300
302
|
expect(result.failureMessage).toContain("network is down");
|
|
301
303
|
for (const call of execFileMock.mock.calls) {
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
ReleaseTerminalResponse,
|
|
22
22
|
RequestPermissionRequest,
|
|
23
23
|
RequestPermissionResponse,
|
|
24
|
+
SessionConfigOption,
|
|
24
25
|
SessionNotification,
|
|
25
26
|
TerminalOutputRequest,
|
|
26
27
|
TerminalOutputResponse,
|
|
@@ -103,6 +104,9 @@ export class VellumAcpClientHandler implements Client {
|
|
|
103
104
|
private readonly acpSessionId: string,
|
|
104
105
|
private readonly sendToVellum: (msg: AssistantEvent) => void,
|
|
105
106
|
private readonly parentConversationId: string,
|
|
107
|
+
private readonly onConfigOptions?: (
|
|
108
|
+
configOptions: SessionConfigOption[],
|
|
109
|
+
) => void,
|
|
106
110
|
) {}
|
|
107
111
|
|
|
108
112
|
/** Forwards an update to Vellum, stamping a contiguous per-session `seq`. */
|
|
@@ -182,7 +186,12 @@ export class VellumAcpClientHandler implements Client {
|
|
|
182
186
|
async sessionUpdate(params: SessionNotification): Promise<void> {
|
|
183
187
|
const update = params.update;
|
|
184
188
|
|
|
185
|
-
|
|
189
|
+
// Config options are current state rather than transcript history, so they
|
|
190
|
+
// are applied even while a replay is being suppressed.
|
|
191
|
+
if (
|
|
192
|
+
this.suppressForwarding &&
|
|
193
|
+
update.sessionUpdate !== "config_option_update"
|
|
194
|
+
) {
|
|
186
195
|
log.debug(
|
|
187
196
|
{ acpSessionId: this.acpSessionId, updateType: update.sessionUpdate },
|
|
188
197
|
"Dropping replayed session update during suppression",
|
|
@@ -286,10 +295,14 @@ export class VellumAcpClientHandler implements Client {
|
|
|
286
295
|
break;
|
|
287
296
|
}
|
|
288
297
|
|
|
298
|
+
case "config_option_update": {
|
|
299
|
+
this.onConfigOptions?.(update.configOptions);
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
|
|
289
303
|
default: {
|
|
290
304
|
// Other update types (available_commands_update, current_mode_update,
|
|
291
|
-
//
|
|
292
|
-
// Vellum.
|
|
305
|
+
// session_info_update) are not forwarded to Vellum.
|
|
293
306
|
log.debug(
|
|
294
307
|
{
|
|
295
308
|
acpSessionId: this.acpSessionId,
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the pure ACP model-config helpers: flattening an adapter's model
|
|
3
|
+
* `SessionConfigOption` into publishable model info, and the spawn-time
|
|
4
|
+
* precedence ladder.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
|
+
|
|
9
|
+
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
MODEL_OPTION_MODELS,
|
|
13
|
+
modelOption,
|
|
14
|
+
} from "./__tests__/helpers/acp-model-option.js";
|
|
15
|
+
import { deriveModelInfo, resolveAcpModel } from "./model-config.js";
|
|
16
|
+
|
|
17
|
+
const flatModelOption = modelOption("sonnet");
|
|
18
|
+
|
|
19
|
+
describe("deriveModelInfo", () => {
|
|
20
|
+
test("flattens flat options and reports the current value", () => {
|
|
21
|
+
expect(deriveModelInfo([flatModelOption])).toEqual({
|
|
22
|
+
model: "sonnet",
|
|
23
|
+
modelConfigId: "model",
|
|
24
|
+
availableModels: MODEL_OPTION_MODELS,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("flattens grouped options and carries the group name", () => {
|
|
29
|
+
const grouped: SessionConfigOption = {
|
|
30
|
+
type: "select",
|
|
31
|
+
id: "model",
|
|
32
|
+
name: "Model",
|
|
33
|
+
category: "model",
|
|
34
|
+
currentValue: "opus",
|
|
35
|
+
options: [
|
|
36
|
+
{
|
|
37
|
+
group: "anthropic",
|
|
38
|
+
name: "Anthropic",
|
|
39
|
+
options: [
|
|
40
|
+
{ value: "opus", name: "Opus" },
|
|
41
|
+
{ value: "haiku", name: "Haiku", description: "Fast" },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
group: "aliases",
|
|
46
|
+
name: "Aliases",
|
|
47
|
+
options: [{ value: "best", name: "Best available" }],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
expect(deriveModelInfo([grouped])).toEqual({
|
|
53
|
+
model: "opus",
|
|
54
|
+
modelConfigId: "model",
|
|
55
|
+
availableModels: [
|
|
56
|
+
{ value: "opus", label: "Opus", group: "Anthropic" },
|
|
57
|
+
{
|
|
58
|
+
value: "haiku",
|
|
59
|
+
label: "Haiku",
|
|
60
|
+
description: "Fast",
|
|
61
|
+
group: "Anthropic",
|
|
62
|
+
},
|
|
63
|
+
{ value: "best", label: "Best available", group: "Aliases" },
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("matches on id alone when no category is reported", () => {
|
|
69
|
+
const uncategorized: SessionConfigOption = {
|
|
70
|
+
type: "select",
|
|
71
|
+
id: "model",
|
|
72
|
+
name: "Model",
|
|
73
|
+
currentValue: "haiku",
|
|
74
|
+
options: [{ value: "haiku", name: "Haiku" }],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const info = deriveModelInfo([uncategorized]);
|
|
78
|
+
|
|
79
|
+
expect(info.modelConfigId).toBe("model");
|
|
80
|
+
expect(info.model).toBe("haiku");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("matches on category alone when the id differs", () => {
|
|
84
|
+
const byCategory: SessionConfigOption = {
|
|
85
|
+
type: "select",
|
|
86
|
+
id: "llm",
|
|
87
|
+
name: "LLM",
|
|
88
|
+
category: "model",
|
|
89
|
+
currentValue: "gpt-5",
|
|
90
|
+
options: [{ value: "gpt-5", name: "GPT-5" }],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
expect(deriveModelInfo([byCategory]).modelConfigId).toBe("llm");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("ignores a boolean option even when its category is model", () => {
|
|
97
|
+
const booleanOption: SessionConfigOption = {
|
|
98
|
+
type: "boolean",
|
|
99
|
+
id: "model",
|
|
100
|
+
name: "Model",
|
|
101
|
+
category: "model",
|
|
102
|
+
currentValue: true,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
expect(deriveModelInfo([booleanOption])).toEqual({ availableModels: [] });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("skips non-model select options and picks the first match", () => {
|
|
109
|
+
const mode: SessionConfigOption = {
|
|
110
|
+
type: "select",
|
|
111
|
+
id: "mode",
|
|
112
|
+
name: "Mode",
|
|
113
|
+
category: "mode",
|
|
114
|
+
currentValue: "ask",
|
|
115
|
+
options: [{ value: "ask", name: "Ask" }],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
expect(deriveModelInfo([mode, flatModelOption]).modelConfigId).toBe(
|
|
119
|
+
"model",
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("returns no model info for an empty array", () => {
|
|
124
|
+
expect(deriveModelInfo([])).toEqual({ availableModels: [] });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("returns no model info for null", () => {
|
|
128
|
+
expect(deriveModelInfo(null)).toEqual({ availableModels: [] });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("returns no model info for undefined", () => {
|
|
132
|
+
expect(deriveModelInfo(undefined)).toEqual({ availableModels: [] });
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("resolveAcpModel", () => {
|
|
137
|
+
test("prefers the requested model over the per-agent one", () => {
|
|
138
|
+
expect(
|
|
139
|
+
resolveAcpModel({ requestedModel: "opus", agentModel: "haiku" }),
|
|
140
|
+
).toBe("opus");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("falls back to the per-agent model", () => {
|
|
144
|
+
expect(resolveAcpModel({ agentModel: "haiku" })).toBe("haiku");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("returns undefined when every rung is unset", () => {
|
|
148
|
+
expect(resolveAcpModel({})).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("treats blank and whitespace-only values as unset", () => {
|
|
152
|
+
expect(
|
|
153
|
+
resolveAcpModel({ requestedModel: "\t\n", agentModel: "haiku" }),
|
|
154
|
+
).toBe("haiku");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("returns undefined when every rung is whitespace", () => {
|
|
158
|
+
expect(
|
|
159
|
+
resolveAcpModel({ requestedModel: " ", agentModel: " " }),
|
|
160
|
+
).toBeUndefined();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("trims the value it returns", () => {
|
|
164
|
+
expect(resolveAcpModel({ requestedModel: " opus " })).toBe("opus");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for ACP session model selection.
|
|
3
|
+
*
|
|
4
|
+
* ACP 0.25.0 has no `session/set_model`: a model is one `SessionConfigOption`
|
|
5
|
+
* among the set an adapter reports from `session/new`, `session/load`,
|
|
6
|
+
* `session/resume`, and `session/set_config_option`. `deriveModelInfo` turns
|
|
7
|
+
* that raw set into the trio the daemon publishes and persists (current model,
|
|
8
|
+
* selectable options, and the config id to write back through
|
|
9
|
+
* `setSessionConfigOption`), flattening the grouped `options` variant so
|
|
10
|
+
* callers never branch on it.
|
|
11
|
+
*
|
|
12
|
+
* The model option is identified by `type === "select"` and `id === "model"`
|
|
13
|
+
* or `category === "model"` only. Option `name` is a human label an adapter
|
|
14
|
+
* may localize or restyle at will, so it is never matched against.
|
|
15
|
+
*
|
|
16
|
+
* `resolveAcpModel` is the precedence ladder a spawn or a resume walks before
|
|
17
|
+
* it talks to the adapter: an explicit request beats the per-agent default from
|
|
18
|
+
* `acp.agents.<id>.model`, and nothing named there leaves the adapter on its
|
|
19
|
+
* own. Values are adapter-reported aliases (`opus`, `sonnet`), never Assistant
|
|
20
|
+
* catalog ids, and are passed through unvalidated: only the adapter knows what
|
|
21
|
+
* it accepts.
|
|
22
|
+
*
|
|
23
|
+
* Both helpers are synchronous and side-effect free.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type {
|
|
27
|
+
SessionConfigOption,
|
|
28
|
+
SessionConfigSelectGroup,
|
|
29
|
+
SessionConfigSelectOption,
|
|
30
|
+
} from "@agentclientprotocol/sdk";
|
|
31
|
+
import type { z } from "zod";
|
|
32
|
+
|
|
33
|
+
import type { AcpSessionModelUpdateEventSchema } from "../api/events/acp-session-model-update.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A selectable model as reported by the adapter, flattened out of its group.
|
|
37
|
+
* Derived from the event schema so the published wire shape and the shape the
|
|
38
|
+
* daemon carries in memory cannot drift apart.
|
|
39
|
+
*/
|
|
40
|
+
export type AcpModelOption = z.infer<
|
|
41
|
+
typeof AcpSessionModelUpdateEventSchema
|
|
42
|
+
>["availableModels"][number];
|
|
43
|
+
|
|
44
|
+
/** What an adapter's config-option set says about the session's model. */
|
|
45
|
+
export type AcpModelInfo = {
|
|
46
|
+
model?: string;
|
|
47
|
+
availableModels: AcpModelOption[];
|
|
48
|
+
modelConfigId?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type ResolveAcpModelInput = {
|
|
52
|
+
requestedModel?: string;
|
|
53
|
+
agentModel?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type ModelSelectOption = Extract<SessionConfigOption, { type: "select" }>;
|
|
57
|
+
|
|
58
|
+
function isGroup(
|
|
59
|
+
entry: SessionConfigSelectOption | SessionConfigSelectGroup,
|
|
60
|
+
): entry is SessionConfigSelectGroup {
|
|
61
|
+
return "group" in entry;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toModelOption(
|
|
65
|
+
option: SessionConfigSelectOption,
|
|
66
|
+
group?: string,
|
|
67
|
+
): AcpModelOption {
|
|
68
|
+
return {
|
|
69
|
+
value: option.value,
|
|
70
|
+
label: option.name,
|
|
71
|
+
...(option.description ? { description: option.description } : {}),
|
|
72
|
+
...(group ? { group } : {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The adapter's model selector, if it advertises one. `category` is UX-only
|
|
78
|
+
* per the ACP spec, so a `model` id counts too; non-select options never do.
|
|
79
|
+
*/
|
|
80
|
+
function findModelConfigOption(
|
|
81
|
+
configOptions: SessionConfigOption[] | null | undefined,
|
|
82
|
+
): ModelSelectOption | undefined {
|
|
83
|
+
return configOptions?.find(
|
|
84
|
+
(option): option is ModelSelectOption =>
|
|
85
|
+
option.type === "select" &&
|
|
86
|
+
(option.id === "model" || option.category === "model"),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Extract current model, selectable models, and the config id to write back.
|
|
92
|
+
* Returns `{ availableModels: [] }` when the adapter advertises no model
|
|
93
|
+
* selector, which is how the whole feature degrades to invisible.
|
|
94
|
+
*/
|
|
95
|
+
export function deriveModelInfo(
|
|
96
|
+
configOptions: SessionConfigOption[] | null | undefined,
|
|
97
|
+
): AcpModelInfo {
|
|
98
|
+
const modelOption = findModelConfigOption(configOptions);
|
|
99
|
+
|
|
100
|
+
if (!modelOption) {
|
|
101
|
+
return { availableModels: [] };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const availableModels: AcpModelOption[] = [];
|
|
105
|
+
for (const entry of modelOption.options) {
|
|
106
|
+
if (isGroup(entry)) {
|
|
107
|
+
for (const option of entry.options) {
|
|
108
|
+
availableModels.push(toModelOption(option, entry.name));
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
availableModels.push(toModelOption(entry));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
...(modelOption.currentValue ? { model: modelOption.currentValue } : {}),
|
|
117
|
+
availableModels,
|
|
118
|
+
modelConfigId: modelOption.id,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Pick the model a session should start on. Blank and whitespace-only values
|
|
124
|
+
* count as unset so an empty config field never pins anything.
|
|
125
|
+
*/
|
|
126
|
+
export function resolveAcpModel(
|
|
127
|
+
input: ResolveAcpModelInput,
|
|
128
|
+
): string | undefined {
|
|
129
|
+
const ladder = [input.requestedModel, input.agentModel];
|
|
130
|
+
|
|
131
|
+
for (const candidate of ladder) {
|
|
132
|
+
const trimmed = candidate?.trim();
|
|
133
|
+
if (trimmed) {
|
|
134
|
+
return trimmed;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|