@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
|
@@ -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) {
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renewal policy for the Claude OAuth credential the ACP spawn path injects as
|
|
3
|
+
* `CLAUDE_CODE_OAUTH_TOKEN`. Storage lives in `acp-claude-oauth.ts`; this
|
|
4
|
+
* module owns only the decision to refresh and the handling of what comes back.
|
|
5
|
+
*
|
|
6
|
+
* Setting `CLAUDE_CODE_OAUTH_TOKEN` tells the Claude Agent SDK to treat the
|
|
7
|
+
* value as a static bearer token: its own credential store and refresh
|
|
8
|
+
* machinery are skipped entirely. The SDK does expose a host-refresh hook,
|
|
9
|
+
* `getOAuthToken`, but `claude-agent-acp` never passes it through and it is
|
|
10
|
+
* absent from the SDK's public typings. With the env-var contract the adapter
|
|
11
|
+
* gives us, the daemon is the only thing that can renew this token.
|
|
12
|
+
*
|
|
13
|
+
* Every failure mode here returns without throwing and lets the spawn proceed
|
|
14
|
+
* with whatever token is stored. A dead token then fails at the adapter as a
|
|
15
|
+
* structured ACP `auth_required`, which raises the Connect card. Renewal is
|
|
16
|
+
* the fast path, not the error path, so it must never be the thing that fails
|
|
17
|
+
* a spawn.
|
|
18
|
+
*
|
|
19
|
+
* When the provider rejects the refresh token itself (revoked, or rotated out
|
|
20
|
+
* from under us), the stored refresh token is dropped. The recorded expiry is
|
|
21
|
+
* kept, because that pair (expired, no way to renew) is what makes
|
|
22
|
+
* `hasAcpClaudeToken()` answer "not connected" and keep the inline Connect
|
|
23
|
+
* card on screen.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
isCredentialError,
|
|
28
|
+
RefreshDeduplicator,
|
|
29
|
+
} from "@vellumai/credential-storage";
|
|
30
|
+
|
|
31
|
+
import { refreshOAuth2Token } from "../security/oauth2.js";
|
|
32
|
+
import { getLogger } from "../util/logger.js";
|
|
33
|
+
import {
|
|
34
|
+
CLAUDE_OAUTH_CONFIG,
|
|
35
|
+
clearAcpClaudeRefreshToken,
|
|
36
|
+
forgetAcpClaudeRenewalStateIfUnbound,
|
|
37
|
+
hasStoredAcpClaudeAccessToken,
|
|
38
|
+
isAcpClaudeTokenExpiring,
|
|
39
|
+
persistRefreshedAcpClaudeTokens,
|
|
40
|
+
readAcpClaudeRefreshToken,
|
|
41
|
+
} from "./acp-claude-oauth.js";
|
|
42
|
+
import { ACP_OAUTH_TOKEN_FIELD } from "./acp-credentials.js";
|
|
43
|
+
import { acpSpawnCredentialDenialReason } from "./prepare-agent-env.js";
|
|
44
|
+
|
|
45
|
+
const log = getLogger("acp:claude-token-refresh");
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Single in-flight refresh across concurrent spawns. Anthropic rotates the
|
|
49
|
+
* refresh token on use, so two parallel refreshes would race and one would
|
|
50
|
+
* invalidate the other's token.
|
|
51
|
+
*/
|
|
52
|
+
const deduplicator = new RefreshDeduplicator();
|
|
53
|
+
const REFRESH_KEY = "acp:claude";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Renew the stored Claude access token if it is expiring and a refresh token
|
|
57
|
+
* is available. Returns without throwing in every failure mode. Never returns
|
|
58
|
+
* the token: the plaintext read boundary stays with the credential broker, so
|
|
59
|
+
* callers re-read through it as usual.
|
|
60
|
+
*/
|
|
61
|
+
export async function ensureFreshAcpClaudeToken(): Promise<void> {
|
|
62
|
+
// An explicit `allowedTools` that omits `acp_spawn` means the broker will
|
|
63
|
+
// deny the read this renewal exists to feed, so there is nothing to gain by
|
|
64
|
+
// spending a refresh token here. Checking first also keeps a passive spawn
|
|
65
|
+
// from touching a credential the workspace has fenced off.
|
|
66
|
+
if (acpSpawnCredentialDenialReason(ACP_OAUTH_TOKEN_FIELD) !== undefined) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Companions can outlive a deleted access token. Renewal exists to replace
|
|
70
|
+
// that field, not to mint a new credential after the user removed it.
|
|
71
|
+
if (!(await hasStoredAcpClaudeAccessToken())) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
await forgetAcpClaudeRenewalStateIfUnbound();
|
|
76
|
+
} catch (err) {
|
|
77
|
+
log.debug(
|
|
78
|
+
{ err },
|
|
79
|
+
"Could not drop unbound Claude OAuth renewal state before refresh",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (!(await isAcpClaudeTokenExpiring())) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const refreshToken = await readAcpClaudeRefreshToken();
|
|
87
|
+
if (!refreshToken) {
|
|
88
|
+
log.info(
|
|
89
|
+
"Claude OAuth token is expiring and no refresh token is stored; " +
|
|
90
|
+
"the spawn will surface auth_required so the user can reconnect",
|
|
91
|
+
);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
await deduplicator.deduplicate(REFRESH_KEY, () => doRefresh(refreshToken));
|
|
97
|
+
} catch (err) {
|
|
98
|
+
log.debug({ err }, "Claude OAuth token refresh did not complete");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function doRefresh(refreshToken: string): Promise<string> {
|
|
103
|
+
log.info("Refreshing the Claude OAuth token for ACP");
|
|
104
|
+
|
|
105
|
+
let result;
|
|
106
|
+
try {
|
|
107
|
+
result = await refreshOAuth2Token(
|
|
108
|
+
CLAUDE_OAUTH_CONFIG.tokenExchangeUrl,
|
|
109
|
+
CLAUDE_OAUTH_CONFIG.clientId,
|
|
110
|
+
refreshToken,
|
|
111
|
+
// PKCE public client, so no secret. Anthropic's token endpoint takes a
|
|
112
|
+
// JSON body, matching the authorization-code exchange.
|
|
113
|
+
undefined,
|
|
114
|
+
undefined,
|
|
115
|
+
CLAUDE_OAUTH_CONFIG.tokenExchangeBodyFormat,
|
|
116
|
+
);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
if (isCredentialError(err)) {
|
|
119
|
+
log.warn(
|
|
120
|
+
{ err },
|
|
121
|
+
"Claude OAuth refresh token was rejected; dropping it so the account reads as needing a reconnect",
|
|
122
|
+
);
|
|
123
|
+
await clearAcpClaudeRefreshToken(refreshToken);
|
|
124
|
+
} else {
|
|
125
|
+
log.warn({ err }, "Claude OAuth token refresh failed transiently");
|
|
126
|
+
}
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!result.accessToken) {
|
|
131
|
+
log.warn(
|
|
132
|
+
"Claude OAuth refresh returned no usable access token; dropping the refresh token so the account reads as needing a reconnect",
|
|
133
|
+
);
|
|
134
|
+
await clearAcpClaudeRefreshToken(refreshToken);
|
|
135
|
+
throw new Error("Claude OAuth refresh returned no access token");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const persisted = await persistRefreshedAcpClaudeTokens(
|
|
139
|
+
{
|
|
140
|
+
accessToken: result.accessToken,
|
|
141
|
+
refreshToken: result.refreshToken,
|
|
142
|
+
expiresIn: result.expiresIn,
|
|
143
|
+
},
|
|
144
|
+
refreshToken,
|
|
145
|
+
);
|
|
146
|
+
if (persisted) {
|
|
147
|
+
log.info("Claude OAuth token refreshed");
|
|
148
|
+
}
|
|
149
|
+
return result.accessToken;
|
|
150
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -199,6 +199,26 @@ export function acpSpawnCredentialDenialReason(
|
|
|
199
199
|
* mode, including a simply-absent credential, as `{ success: false,
|
|
200
200
|
* reason }`, so callers choose whether a miss is fatal.
|
|
201
201
|
*/
|
|
202
|
+
/**
|
|
203
|
+
* Vault path for `CLAUDE_CODE_OAUTH_TOKEN`. Renews first so a near-expired
|
|
204
|
+
* stored token is replaced before the broker read. Config overrides never
|
|
205
|
+
* reach here, so this does not spend the vault refresh token on a spawn
|
|
206
|
+
* that will not use it.
|
|
207
|
+
*/
|
|
208
|
+
async function injectClaudeOauthFromVault(
|
|
209
|
+
env: Record<string, string>,
|
|
210
|
+
): Promise<string | undefined> {
|
|
211
|
+
const { ensureFreshAcpClaudeToken } =
|
|
212
|
+
await import("./claude-token-refresh.js");
|
|
213
|
+
await ensureFreshAcpClaudeToken();
|
|
214
|
+
return injectCredential(
|
|
215
|
+
env,
|
|
216
|
+
ACP_OAUTH_TOKEN_FIELD,
|
|
217
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
218
|
+
ACP_CLAUDE_OAUTH_USAGE_DESCRIPTION,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
202
222
|
async function injectCredential(
|
|
203
223
|
env: Record<string, string>,
|
|
204
224
|
field: string,
|
|
@@ -371,12 +391,7 @@ export async function prepareAgentEnv(
|
|
|
371
391
|
}
|
|
372
392
|
let missReason: string | undefined;
|
|
373
393
|
if (!env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
374
|
-
missReason = await
|
|
375
|
-
env,
|
|
376
|
-
ACP_OAUTH_TOKEN_FIELD,
|
|
377
|
-
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
378
|
-
ACP_CLAUDE_OAUTH_USAGE_DESCRIPTION,
|
|
379
|
-
);
|
|
394
|
+
missReason = await injectClaudeOauthFromVault(env);
|
|
380
395
|
}
|
|
381
396
|
// Any api-key-shaped value still standing here came from the vault read:
|
|
382
397
|
// the config override was already dropped above, and the read only runs
|