@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
|
@@ -22,11 +22,38 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
23
23
|
|
|
24
24
|
let storeReturn = true;
|
|
25
|
+
let deleteReturn: "deleted" | "not-found" | "error" = "not-found";
|
|
25
26
|
let getReturn: string | undefined = undefined;
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
27
|
+
const vault = new Map<string, string>();
|
|
28
|
+
const ACCESS_KEY = "credential/acp/claude_oauth_token";
|
|
29
|
+
const REFRESH_KEY = "credential/acp/claude_oauth_refresh_token";
|
|
30
|
+
const EXPIRES_KEY = "credential/acp/claude_oauth_expires_at";
|
|
31
|
+
const DIGEST_KEY = "credential/acp/claude_oauth_access_digest";
|
|
32
|
+
|
|
33
|
+
const setSecureKeyAsync = mock(async (account: string, value: string) => {
|
|
34
|
+
if (!storeReturn) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
vault.set(account, value);
|
|
38
|
+
return true;
|
|
39
|
+
});
|
|
40
|
+
const getSecureKeyAsync = mock(async (account: string) => {
|
|
41
|
+
if (vault.has(account)) {
|
|
42
|
+
return vault.get(account);
|
|
43
|
+
}
|
|
44
|
+
// Existing cases that only seed the access token keep using getReturn.
|
|
45
|
+
if (account === ACCESS_KEY) {
|
|
46
|
+
return getReturn;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
});
|
|
50
|
+
const deleteSecureKeyAsync = mock(async (account: string) => {
|
|
51
|
+
if (deleteReturn === "error") {
|
|
52
|
+
return "error";
|
|
53
|
+
}
|
|
54
|
+
const existed = vault.delete(account);
|
|
55
|
+
return existed ? "deleted" : "not-found";
|
|
56
|
+
});
|
|
30
57
|
|
|
31
58
|
// Spread the real module rather than listing two exports. These cases reach
|
|
32
59
|
// the marker tables, which pulls persistence into the graph, and anything in
|
|
@@ -37,6 +64,7 @@ mock.module("../../security/secure-keys.js", () => ({
|
|
|
37
64
|
...realSecureKeys,
|
|
38
65
|
setSecureKeyAsync,
|
|
39
66
|
getSecureKeyAsync,
|
|
67
|
+
deleteSecureKeyAsync,
|
|
40
68
|
}));
|
|
41
69
|
|
|
42
70
|
const { _setMetadataPath, getCredentialMetadata, upsertCredentialMetadata } =
|
|
@@ -69,6 +97,10 @@ const {
|
|
|
69
97
|
parseManualClaudeCode,
|
|
70
98
|
storeAcpClaudeToken,
|
|
71
99
|
hasAcpClaudeToken,
|
|
100
|
+
persistRefreshedAcpClaudeTokens,
|
|
101
|
+
forgetAcpClaudeRenewalStateIfUnbound,
|
|
102
|
+
clearAcpClaudeRefreshToken,
|
|
103
|
+
isAcpClaudeTokenExpiring,
|
|
72
104
|
} = await import("../acp-claude-oauth.js");
|
|
73
105
|
|
|
74
106
|
/**
|
|
@@ -95,9 +127,12 @@ beforeEach(() => {
|
|
|
95
127
|
mkdirSync(TEST_DIR, { recursive: true });
|
|
96
128
|
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
97
129
|
storeReturn = true;
|
|
130
|
+
deleteReturn = "not-found";
|
|
98
131
|
getReturn = undefined;
|
|
132
|
+
vault.clear();
|
|
99
133
|
setSecureKeyAsync.mockClear();
|
|
100
134
|
getSecureKeyAsync.mockClear();
|
|
135
|
+
deleteSecureKeyAsync.mockClear();
|
|
101
136
|
});
|
|
102
137
|
|
|
103
138
|
afterEach(() => {
|
|
@@ -204,11 +239,43 @@ describe("storeAcpClaudeToken", () => {
|
|
|
204
239
|
test("writes the token to the acp/claude_oauth_token vault field", async () => {
|
|
205
240
|
await storeAcpClaudeToken("sk-ant-oat-token");
|
|
206
241
|
|
|
207
|
-
expect(setSecureKeyAsync).toHaveBeenCalledTimes(1);
|
|
208
242
|
expect(setSecureKeyAsync).toHaveBeenCalledWith(
|
|
209
|
-
|
|
243
|
+
ACCESS_KEY,
|
|
210
244
|
"sk-ant-oat-token",
|
|
211
245
|
);
|
|
246
|
+
expect(vault.get(ACCESS_KEY)).toBe("sk-ant-oat-token");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("persists refresh token and expiry from a Connect exchange", async () => {
|
|
250
|
+
const before = Date.now();
|
|
251
|
+
await storeAcpClaudeToken({
|
|
252
|
+
accessToken: "sk-ant-oat-connected",
|
|
253
|
+
refreshToken: "refresh-from-exchange",
|
|
254
|
+
expiresIn: 28800,
|
|
255
|
+
});
|
|
256
|
+
const after = Date.now();
|
|
257
|
+
|
|
258
|
+
expect(vault.get(ACCESS_KEY)).toBe("sk-ant-oat-connected");
|
|
259
|
+
expect(vault.get(REFRESH_KEY)).toBe("refresh-from-exchange");
|
|
260
|
+
expect(vault.get(DIGEST_KEY)).toBe(
|
|
261
|
+
claudeTokenDigest("sk-ant-oat-connected"),
|
|
262
|
+
);
|
|
263
|
+
const expiresAt = Number(vault.get(EXPIRES_KEY));
|
|
264
|
+
expect(expiresAt).toBeGreaterThanOrEqual(before + 28800 * 1000);
|
|
265
|
+
expect(expiresAt).toBeLessThanOrEqual(after + 28800 * 1000);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("clears companion fields when the exchange returns only an access token", async () => {
|
|
269
|
+
vault.set(REFRESH_KEY, "stale-refresh");
|
|
270
|
+
vault.set(EXPIRES_KEY, "1");
|
|
271
|
+
vault.set(DIGEST_KEY, "stale-digest");
|
|
272
|
+
|
|
273
|
+
await storeAcpClaudeToken({ accessToken: "sk-ant-oat-access-only" });
|
|
274
|
+
|
|
275
|
+
expect(vault.get(ACCESS_KEY)).toBe("sk-ant-oat-access-only");
|
|
276
|
+
expect(vault.has(REFRESH_KEY)).toBe(false);
|
|
277
|
+
expect(vault.has(EXPIRES_KEY)).toBe(false);
|
|
278
|
+
expect(vault.has(DIGEST_KEY)).toBe(false);
|
|
212
279
|
});
|
|
213
280
|
|
|
214
281
|
test("takes a domain-restricted credential from not-connected to connected", async () => {
|
|
@@ -309,6 +376,190 @@ describe("hasAcpClaudeToken", () => {
|
|
|
309
376
|
|
|
310
377
|
expect(oauthMetadata()).toBeUndefined();
|
|
311
378
|
});
|
|
379
|
+
|
|
380
|
+
test("reports true for an expired token that still has a refresh token", async () => {
|
|
381
|
+
vault.set(ACCESS_KEY, "sk-ant-oat-expired-renewable");
|
|
382
|
+
vault.set(REFRESH_KEY, "refresh-still-good");
|
|
383
|
+
vault.set(EXPIRES_KEY, String(Date.now() - 1000));
|
|
384
|
+
|
|
385
|
+
expect(await hasAcpClaudeToken()).toBe(true);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("reports false for an expired token with no refresh token", async () => {
|
|
389
|
+
vault.set(ACCESS_KEY, "sk-ant-oat-expired-dead");
|
|
390
|
+
vault.set(EXPIRES_KEY, String(Date.now() - 1000));
|
|
391
|
+
|
|
392
|
+
expect(await hasAcpClaudeToken()).toBe(false);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("reports true for a token with no recorded expiry (legacy access-token-only)", async () => {
|
|
396
|
+
vault.set(ACCESS_KEY, "sk-ant-oat-legacy");
|
|
397
|
+
|
|
398
|
+
expect(await hasAcpClaudeToken()).toBe(true);
|
|
399
|
+
expect(await isAcpClaudeTokenExpiring()).toBe(false);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("reports false when only leftover refresh material remains", async () => {
|
|
403
|
+
vault.set(REFRESH_KEY, "refresh-leftover");
|
|
404
|
+
vault.set(EXPIRES_KEY, String(Date.now() - 1000));
|
|
405
|
+
|
|
406
|
+
expect(await hasAcpClaudeToken()).toBe(false);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("reports true for a pasted token whose leftover expiry describes a previous token", async () => {
|
|
410
|
+
vault.set(ACCESS_KEY, "sk-ant-oat-pasted");
|
|
411
|
+
vault.set(REFRESH_KEY, "refresh-previous");
|
|
412
|
+
vault.set(EXPIRES_KEY, String(Date.now() - 1000));
|
|
413
|
+
vault.set(DIGEST_KEY, claudeTokenDigest("sk-ant-oat-previous"));
|
|
414
|
+
|
|
415
|
+
expect(await hasAcpClaudeToken()).toBe(true);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
describe("persistRefreshedAcpClaudeTokens", () => {
|
|
420
|
+
test("writes a rotated refresh token and new expiry without touching policy", async () => {
|
|
421
|
+
upsertCredentialMetadata(ACP_SERVICE, OAUTH_FIELD, {
|
|
422
|
+
allowedTools: ["some_other_tool"],
|
|
423
|
+
});
|
|
424
|
+
vault.set(ACCESS_KEY, "sk-ant-oat-current");
|
|
425
|
+
vault.set(REFRESH_KEY, "refresh-expected");
|
|
426
|
+
vault.set(DIGEST_KEY, claudeTokenDigest("sk-ant-oat-current"));
|
|
427
|
+
const before = Date.now();
|
|
428
|
+
|
|
429
|
+
await persistRefreshedAcpClaudeTokens(
|
|
430
|
+
{
|
|
431
|
+
accessToken: "sk-ant-oat-refreshed",
|
|
432
|
+
refreshToken: "refresh-rotated",
|
|
433
|
+
expiresIn: 3600,
|
|
434
|
+
},
|
|
435
|
+
"refresh-expected",
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
expect(vault.get(ACCESS_KEY)).toBe("sk-ant-oat-refreshed");
|
|
439
|
+
expect(vault.get(REFRESH_KEY)).toBe("refresh-rotated");
|
|
440
|
+
expect(vault.get(DIGEST_KEY)).toBe(
|
|
441
|
+
claudeTokenDigest("sk-ant-oat-refreshed"),
|
|
442
|
+
);
|
|
443
|
+
const expiresAt = Number(vault.get(EXPIRES_KEY));
|
|
444
|
+
expect(expiresAt).toBeGreaterThanOrEqual(before + 3600 * 1000);
|
|
445
|
+
expect(oauthMetadata()?.allowedTools).toEqual(["some_other_tool"]);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
test("keeps the stored refresh token when the response omits a new one", async () => {
|
|
449
|
+
vault.set(ACCESS_KEY, "sk-ant-oat-current");
|
|
450
|
+
vault.set(REFRESH_KEY, "refresh-kept");
|
|
451
|
+
vault.set(DIGEST_KEY, claudeTokenDigest("sk-ant-oat-current"));
|
|
452
|
+
|
|
453
|
+
await persistRefreshedAcpClaudeTokens(
|
|
454
|
+
{
|
|
455
|
+
accessToken: "sk-ant-oat-refreshed",
|
|
456
|
+
expiresIn: 3600,
|
|
457
|
+
},
|
|
458
|
+
"refresh-kept",
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
expect(vault.get(ACCESS_KEY)).toBe("sk-ant-oat-refreshed");
|
|
462
|
+
expect(vault.get(REFRESH_KEY)).toBe("refresh-kept");
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test("refuses to persist when the stored refresh token no longer matches", async () => {
|
|
466
|
+
vault.set(ACCESS_KEY, "sk-ant-oat-current");
|
|
467
|
+
vault.set(REFRESH_KEY, "refresh-current");
|
|
468
|
+
vault.set(EXPIRES_KEY, "999");
|
|
469
|
+
|
|
470
|
+
const persisted = await persistRefreshedAcpClaudeTokens(
|
|
471
|
+
{
|
|
472
|
+
accessToken: "sk-ant-oat-stale",
|
|
473
|
+
refreshToken: "refresh-stale-rotated",
|
|
474
|
+
expiresIn: 3600,
|
|
475
|
+
},
|
|
476
|
+
"refresh-old",
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
expect(persisted).toBe(false);
|
|
480
|
+
expect(vault.get(ACCESS_KEY)).toBe("sk-ant-oat-current");
|
|
481
|
+
expect(vault.get(REFRESH_KEY)).toBe("refresh-current");
|
|
482
|
+
expect(vault.get(EXPIRES_KEY)).toBe("999");
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
test("refuses to persist when the access token is no longer stored", async () => {
|
|
486
|
+
vault.set(REFRESH_KEY, "refresh-leftover");
|
|
487
|
+
vault.set(EXPIRES_KEY, "111");
|
|
488
|
+
|
|
489
|
+
const persisted = await persistRefreshedAcpClaudeTokens(
|
|
490
|
+
{
|
|
491
|
+
accessToken: "sk-ant-oat-resurrected",
|
|
492
|
+
refreshToken: "refresh-rotated",
|
|
493
|
+
expiresIn: 3600,
|
|
494
|
+
},
|
|
495
|
+
"refresh-leftover",
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
expect(persisted).toBe(false);
|
|
499
|
+
expect(vault.has(ACCESS_KEY)).toBe(false);
|
|
500
|
+
expect(vault.get(REFRESH_KEY)).toBe("refresh-leftover");
|
|
501
|
+
expect(vault.get(EXPIRES_KEY)).toBe("111");
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test("refuses to persist when the access token is not the one the refresh material was written with", async () => {
|
|
505
|
+
vault.set(ACCESS_KEY, "sk-ant-oat-pasted");
|
|
506
|
+
vault.set(REFRESH_KEY, "refresh-previous");
|
|
507
|
+
vault.set(EXPIRES_KEY, "111");
|
|
508
|
+
vault.set(DIGEST_KEY, claudeTokenDigest("sk-ant-oat-previous"));
|
|
509
|
+
|
|
510
|
+
const persisted = await persistRefreshedAcpClaudeTokens(
|
|
511
|
+
{
|
|
512
|
+
accessToken: "sk-ant-oat-overwritten",
|
|
513
|
+
refreshToken: "refresh-rotated",
|
|
514
|
+
expiresIn: 3600,
|
|
515
|
+
},
|
|
516
|
+
"refresh-previous",
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
expect(persisted).toBe(false);
|
|
520
|
+
expect(vault.get(ACCESS_KEY)).toBe("sk-ant-oat-pasted");
|
|
521
|
+
expect(vault.get(REFRESH_KEY)).toBe("refresh-previous");
|
|
522
|
+
expect(vault.get(EXPIRES_KEY)).toBe("111");
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
describe("clearAcpClaudeRefreshToken", () => {
|
|
527
|
+
test("leaves the stored refresh token when it no longer matches the rejected one", async () => {
|
|
528
|
+
vault.set(REFRESH_KEY, "refresh-current");
|
|
529
|
+
|
|
530
|
+
await clearAcpClaudeRefreshToken("refresh-old");
|
|
531
|
+
|
|
532
|
+
expect(vault.get(REFRESH_KEY)).toBe("refresh-current");
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
describe("forgetAcpClaudeRenewalStateIfUnbound", () => {
|
|
537
|
+
test("clears leftover renewal fields after the access token was replaced", async () => {
|
|
538
|
+
vault.set(ACCESS_KEY, "sk-ant-oat-pasted");
|
|
539
|
+
vault.set(REFRESH_KEY, "stale-refresh");
|
|
540
|
+
vault.set(EXPIRES_KEY, "111");
|
|
541
|
+
vault.set(DIGEST_KEY, claudeTokenDigest("sk-ant-oat-previous"));
|
|
542
|
+
|
|
543
|
+
await forgetAcpClaudeRenewalStateIfUnbound();
|
|
544
|
+
|
|
545
|
+
expect(vault.get(ACCESS_KEY)).toBe("sk-ant-oat-pasted");
|
|
546
|
+
expect(vault.has(REFRESH_KEY)).toBe(false);
|
|
547
|
+
expect(vault.has(EXPIRES_KEY)).toBe(false);
|
|
548
|
+
expect(vault.has(DIGEST_KEY)).toBe(false);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("keeps renewal fields when they still describe the stored access token", async () => {
|
|
552
|
+
vault.set(ACCESS_KEY, "sk-ant-oat-current");
|
|
553
|
+
vault.set(REFRESH_KEY, "keep-refresh");
|
|
554
|
+
vault.set(EXPIRES_KEY, "222");
|
|
555
|
+
vault.set(DIGEST_KEY, claudeTokenDigest("sk-ant-oat-current"));
|
|
556
|
+
|
|
557
|
+
await forgetAcpClaudeRenewalStateIfUnbound();
|
|
558
|
+
|
|
559
|
+
expect(vault.get(REFRESH_KEY)).toBe("keep-refresh");
|
|
560
|
+
expect(vault.get(EXPIRES_KEY)).toBe("222");
|
|
561
|
+
expect(vault.get(DIGEST_KEY)).toBe(claudeTokenDigest("sk-ant-oat-current"));
|
|
562
|
+
});
|
|
312
563
|
});
|
|
313
564
|
|
|
314
565
|
describe("storeAcpClaudeToken: retiring the card registry", () => {
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
import { describe, expect, test } from "bun:test";
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
+
ACP_OAUTH_ACCESS_DIGEST_FIELD,
|
|
9
|
+
ACP_OAUTH_EXPIRES_AT_FIELD,
|
|
10
|
+
ACP_OAUTH_REFRESH_TOKEN_FIELD,
|
|
8
11
|
ACP_OAUTH_TOKEN_FIELD,
|
|
9
12
|
ACP_SERVICE,
|
|
10
13
|
AcpCredentialFormatError,
|
|
@@ -70,6 +73,9 @@ describe("constants", () => {
|
|
|
70
73
|
test("expose the ACP service and OAuth field names", () => {
|
|
71
74
|
expect(ACP_SERVICE).toBe("acp");
|
|
72
75
|
expect(ACP_OAUTH_TOKEN_FIELD).toBe("claude_oauth_token");
|
|
76
|
+
expect(ACP_OAUTH_REFRESH_TOKEN_FIELD).toBe("claude_oauth_refresh_token");
|
|
77
|
+
expect(ACP_OAUTH_EXPIRES_AT_FIELD).toBe("claude_oauth_expires_at");
|
|
78
|
+
expect(ACP_OAUTH_ACCESS_DIGEST_FIELD).toBe("claude_oauth_access_digest");
|
|
73
79
|
});
|
|
74
80
|
});
|
|
75
81
|
|
|
@@ -81,6 +87,13 @@ describe("isAcpClaudeOauthField", () => {
|
|
|
81
87
|
test("rejects other services and fields", () => {
|
|
82
88
|
expect(isAcpClaudeOauthField("acp", "openai_api_key")).toBe(false);
|
|
83
89
|
expect(isAcpClaudeOauthField("acp", "codex_api_key")).toBe(false);
|
|
90
|
+
expect(isAcpClaudeOauthField("acp", "claude_oauth_refresh_token")).toBe(
|
|
91
|
+
false,
|
|
92
|
+
);
|
|
93
|
+
expect(isAcpClaudeOauthField("acp", "claude_oauth_expires_at")).toBe(false);
|
|
94
|
+
expect(isAcpClaudeOauthField("acp", "claude_oauth_access_digest")).toBe(
|
|
95
|
+
false,
|
|
96
|
+
);
|
|
84
97
|
expect(isAcpClaudeOauthField("sentry", "claude_oauth_token")).toBe(false);
|
|
85
98
|
expect(isAcpClaudeOauthField("openai", "api_key")).toBe(false);
|
|
86
99
|
});
|
|
@@ -8,9 +8,16 @@
|
|
|
8
8
|
|
|
9
9
|
import { describe, expect, mock, test } from "bun:test";
|
|
10
10
|
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
AuthMethod,
|
|
13
|
+
InitializeResponse,
|
|
14
|
+
SessionConfigOption,
|
|
15
|
+
} from "@agentclientprotocol/sdk";
|
|
16
|
+
import * as acp from "@agentclientprotocol/sdk";
|
|
12
17
|
|
|
13
18
|
import { AcpAgentProcess } from "../agent-process.js";
|
|
19
|
+
import { AcpConfigOptionRefusedError } from "../types.js";
|
|
20
|
+
import { modelOption } from "./helpers/acp-model-option.js";
|
|
14
21
|
|
|
15
22
|
function makeProcess(): AcpAgentProcess {
|
|
16
23
|
return new AcpAgentProcess(
|
|
@@ -160,6 +167,139 @@ describe("AcpAgentProcess loadSession/resumeSession", () => {
|
|
|
160
167
|
});
|
|
161
168
|
});
|
|
162
169
|
|
|
170
|
+
describe("AcpAgentProcess config options", () => {
|
|
171
|
+
/** Injects a connection whose session calls report `configOptions`. */
|
|
172
|
+
function stubSessionResponses(
|
|
173
|
+
proc: AcpAgentProcess,
|
|
174
|
+
configOptions: SessionConfigOption[] | null | undefined,
|
|
175
|
+
): void {
|
|
176
|
+
(proc as unknown as { connection: unknown }).connection = {
|
|
177
|
+
newSession: () =>
|
|
178
|
+
Promise.resolve({ sessionId: "session-1", configOptions }),
|
|
179
|
+
loadSession: () => Promise.resolve({ configOptions }),
|
|
180
|
+
resumeSession: () => Promise.resolve({ configOptions }),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
test("createSession returns the reported options", async () => {
|
|
185
|
+
const proc = makeProcess();
|
|
186
|
+
const options = [modelOption("sonnet")];
|
|
187
|
+
stubSessionResponses(proc, options);
|
|
188
|
+
|
|
189
|
+
await expect(proc.createSession("/tmp/project")).resolves.toEqual({
|
|
190
|
+
sessionId: "session-1",
|
|
191
|
+
configOptions: options,
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("loadSession returns the reported options", async () => {
|
|
196
|
+
const proc = makeProcess();
|
|
197
|
+
const options = [modelOption("sonnet")];
|
|
198
|
+
stubSessionResponses(proc, options);
|
|
199
|
+
|
|
200
|
+
await expect(
|
|
201
|
+
proc.loadSession("session-1", "/tmp/project"),
|
|
202
|
+
).resolves.toEqual({ configOptions: options });
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("resumeSession returns the reported options", async () => {
|
|
206
|
+
const proc = makeProcess();
|
|
207
|
+
const options = [modelOption("opus")];
|
|
208
|
+
stubSessionResponses(proc, options);
|
|
209
|
+
|
|
210
|
+
await expect(
|
|
211
|
+
proc.resumeSession("session-1", "/tmp/project"),
|
|
212
|
+
).resolves.toEqual({ configOptions: options });
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("a null or absent configOptions normalizes to an empty array", async () => {
|
|
216
|
+
const created = makeProcess();
|
|
217
|
+
stubSessionResponses(created, null);
|
|
218
|
+
await expect(created.createSession("/tmp/project")).resolves.toEqual({
|
|
219
|
+
sessionId: "session-1",
|
|
220
|
+
configOptions: [],
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const loaded = makeProcess();
|
|
224
|
+
stubSessionResponses(loaded, null);
|
|
225
|
+
await expect(
|
|
226
|
+
loaded.loadSession("session-1", "/tmp/project"),
|
|
227
|
+
).resolves.toEqual({ configOptions: [] });
|
|
228
|
+
|
|
229
|
+
const resumed = makeProcess();
|
|
230
|
+
stubSessionResponses(resumed, undefined);
|
|
231
|
+
await expect(
|
|
232
|
+
resumed.resumeSession("session-1", "/tmp/project"),
|
|
233
|
+
).resolves.toEqual({ configOptions: [] });
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("setConfigOption forwards the value and returns the refreshed set", async () => {
|
|
237
|
+
const proc = makeProcess();
|
|
238
|
+
const calls: unknown[] = [];
|
|
239
|
+
const refreshed = [modelOption("opus")];
|
|
240
|
+
(proc as unknown as { connection: unknown }).connection = {
|
|
241
|
+
setSessionConfigOption: (params: unknown) => {
|
|
242
|
+
calls.push(params);
|
|
243
|
+
return Promise.resolve({ configOptions: refreshed });
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
await expect(
|
|
248
|
+
proc.setConfigOption("session-1", "model", "opus"),
|
|
249
|
+
).resolves.toEqual(refreshed);
|
|
250
|
+
|
|
251
|
+
expect(calls).toEqual([
|
|
252
|
+
{ sessionId: "session-1", configId: "model", value: "opus" },
|
|
253
|
+
]);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("setConfigOption sends the boolean request variant", async () => {
|
|
257
|
+
const proc = makeProcess();
|
|
258
|
+
const calls: unknown[] = [];
|
|
259
|
+
const refreshed = [modelOption("opus")];
|
|
260
|
+
(proc as unknown as { connection: unknown }).connection = {
|
|
261
|
+
setSessionConfigOption: (params: unknown) => {
|
|
262
|
+
calls.push(params);
|
|
263
|
+
return Promise.resolve({ configOptions: refreshed });
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
await proc.setConfigOption("session-1", "thinking", true);
|
|
268
|
+
|
|
269
|
+
expect(calls).toEqual([
|
|
270
|
+
{
|
|
271
|
+
sessionId: "session-1",
|
|
272
|
+
configId: "thinking",
|
|
273
|
+
type: "boolean",
|
|
274
|
+
value: true,
|
|
275
|
+
},
|
|
276
|
+
]);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("setConfigOption omits the discriminator for string values", async () => {
|
|
280
|
+
const proc = makeProcess();
|
|
281
|
+
const calls: unknown[] = [];
|
|
282
|
+
(proc as unknown as { connection: unknown }).connection = {
|
|
283
|
+
setSessionConfigOption: (params: unknown) => {
|
|
284
|
+
calls.push(params);
|
|
285
|
+
return Promise.resolve({ configOptions: [modelOption("opus")] });
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
await proc.setConfigOption("session-1", "model", "opus");
|
|
290
|
+
|
|
291
|
+
expect(calls[0]).not.toHaveProperty("type");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("setConfigOption throws when the process is not spawned", async () => {
|
|
295
|
+
const proc = makeProcess();
|
|
296
|
+
|
|
297
|
+
await expect(
|
|
298
|
+
proc.setConfigOption("session-1", "model", "opus"),
|
|
299
|
+
).rejects.toThrow('ACP agent "test-agent" is not spawned');
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
163
303
|
describe("AcpAgentProcess auth_required retry", () => {
|
|
164
304
|
// spawnedEnv is injected the way spawn() builds it ({ ...process.env,
|
|
165
305
|
// ...config.env }), so the advertised var names use a VELLUM_TEST_ prefix
|
|
@@ -196,17 +336,19 @@ describe("AcpAgentProcess auth_required retry", () => {
|
|
|
196
336
|
* initialize() so the advertised authMethods are captured.
|
|
197
337
|
*/
|
|
198
338
|
async function setupAuthProcess(options: {
|
|
339
|
+
command?: string;
|
|
199
340
|
env?: Record<string, string>;
|
|
200
341
|
authMethods?: AuthMethod[];
|
|
201
342
|
/** Rejections to throw from successive newSession calls before succeeding. */
|
|
202
343
|
newSessionRejections?: unknown[];
|
|
203
344
|
loadSessionRejections?: unknown[];
|
|
204
345
|
resumeSessionRejections?: unknown[];
|
|
346
|
+
setConfigOptionRejections?: unknown[];
|
|
205
347
|
promptRejections?: unknown[];
|
|
206
348
|
}) {
|
|
207
349
|
const proc = new AcpAgentProcess(
|
|
208
350
|
"codex",
|
|
209
|
-
{ command: "echo", args: [], env: options.env },
|
|
351
|
+
{ command: options.command ?? "echo", args: [], env: options.env },
|
|
210
352
|
() => {
|
|
211
353
|
throw new Error("client factory should not be called in this test");
|
|
212
354
|
},
|
|
@@ -234,6 +376,10 @@ describe("AcpAgentProcess auth_required retry", () => {
|
|
|
234
376
|
options.resumeSessionRejections ?? [],
|
|
235
377
|
{},
|
|
236
378
|
);
|
|
379
|
+
const setSessionConfigOption = failThenSucceed(
|
|
380
|
+
options.setConfigOptionRejections ?? [],
|
|
381
|
+
{ configOptions: [] },
|
|
382
|
+
);
|
|
237
383
|
const prompt = failThenSucceed(options.promptRejections ?? [], {
|
|
238
384
|
stopReason: "end_turn",
|
|
239
385
|
});
|
|
@@ -252,6 +398,7 @@ describe("AcpAgentProcess auth_required retry", () => {
|
|
|
252
398
|
newSession,
|
|
253
399
|
loadSession,
|
|
254
400
|
resumeSession,
|
|
401
|
+
setSessionConfigOption,
|
|
255
402
|
prompt,
|
|
256
403
|
authenticate,
|
|
257
404
|
};
|
|
@@ -264,18 +411,112 @@ describe("AcpAgentProcess auth_required retry", () => {
|
|
|
264
411
|
newSession,
|
|
265
412
|
loadSession,
|
|
266
413
|
resumeSession,
|
|
414
|
+
setSessionConfigOption,
|
|
267
415
|
prompt,
|
|
268
416
|
authenticate,
|
|
269
417
|
internals,
|
|
270
418
|
};
|
|
271
419
|
}
|
|
272
420
|
|
|
421
|
+
// An adapter that throws a plain Error reaches the client as a generic
|
|
422
|
+
// "Internal error" carrying the real sentence in `data.details`, which is
|
|
423
|
+
// the shape these fixtures reproduce.
|
|
424
|
+
test("setConfigOption surfaces the adapter's error answer as a refusal", async () => {
|
|
425
|
+
const { proc } = await setupAuthProcess({
|
|
426
|
+
setConfigOptionRejections: [
|
|
427
|
+
new acp.RequestError(-32603, "Internal error", {
|
|
428
|
+
details: "Invalid value for config option model: nope",
|
|
429
|
+
}),
|
|
430
|
+
],
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const refusal = await proc
|
|
434
|
+
.setConfigOption("session-1", "model", "nope")
|
|
435
|
+
.catch((err: unknown) => err);
|
|
436
|
+
|
|
437
|
+
expect(refusal).toBeInstanceOf(AcpConfigOptionRefusedError);
|
|
438
|
+
// The adapter's sentence, not the message the SDK framed it in.
|
|
439
|
+
expect((refusal as Error).message).toBe(
|
|
440
|
+
"Invalid value for config option model: nope",
|
|
441
|
+
);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test("setConfigOption serializes a payload that names no reason", async () => {
|
|
445
|
+
const { proc } = await setupAuthProcess({
|
|
446
|
+
setConfigOptionRejections: [
|
|
447
|
+
new acp.RequestError(-32603, "Internal error", { code: 7 }),
|
|
448
|
+
],
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
await expect(
|
|
452
|
+
proc.setConfigOption("session-1", "model", "nope"),
|
|
453
|
+
).rejects.toThrow('{"code":7}');
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test("setConfigOption falls back to the message when there is no payload", async () => {
|
|
457
|
+
const { proc } = await setupAuthProcess({
|
|
458
|
+
setConfigOptionRejections: [
|
|
459
|
+
new acp.RequestError(-32603, "Session not found"),
|
|
460
|
+
],
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
await expect(
|
|
464
|
+
proc.setConfigOption("session-1", "model", "nope"),
|
|
465
|
+
).rejects.toThrow("Session not found");
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test.each(["claude-agent-acp", "/usr/local/bin/claude-agent-acp"])(
|
|
469
|
+
"setConfigOption leaves Claude's message-shaped auth failure untyped (%s)",
|
|
470
|
+
async (command) => {
|
|
471
|
+
const expired = new acp.RequestError(-32603, "Internal error", {
|
|
472
|
+
details: "Not logged in",
|
|
473
|
+
});
|
|
474
|
+
const { proc } = await setupAuthProcess({
|
|
475
|
+
command,
|
|
476
|
+
setConfigOptionRejections: [expired],
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
await expect(
|
|
480
|
+
proc.setConfigOption("session-1", "model", "opus"),
|
|
481
|
+
).rejects.toBe(expired);
|
|
482
|
+
},
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
test("setConfigOption reads Claude's login wording from another adapter as a refusal", async () => {
|
|
486
|
+
const { proc } = await setupAuthProcess({
|
|
487
|
+
command: "codex-acp",
|
|
488
|
+
setConfigOptionRejections: [
|
|
489
|
+
new acp.RequestError(-32603, "Internal error", {
|
|
490
|
+
details: "Not logged in",
|
|
491
|
+
}),
|
|
492
|
+
],
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const refusal = await proc
|
|
496
|
+
.setConfigOption("session-1", "model", "gpt-5")
|
|
497
|
+
.catch((err: unknown) => err);
|
|
498
|
+
|
|
499
|
+
expect(refusal).toBeInstanceOf(AcpConfigOptionRefusedError);
|
|
500
|
+
expect((refusal as Error).message).toBe("Not logged in");
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
test("setConfigOption leaves a transport failure untyped", async () => {
|
|
504
|
+
const closed = new Error("ACP connection closed");
|
|
505
|
+
const { proc } = await setupAuthProcess({
|
|
506
|
+
setConfigOptionRejections: [closed],
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
await expect(
|
|
510
|
+
proc.setConfigOption("session-1", "model", "opus"),
|
|
511
|
+
).rejects.toBe(closed);
|
|
512
|
+
});
|
|
513
|
+
|
|
273
514
|
test("createSession does not authenticate when newSession succeeds", async () => {
|
|
274
515
|
const { proc, newSession, authenticate } = await setupAuthProcess({
|
|
275
516
|
env: { [OPENAI_VAR]: "sk-test" },
|
|
276
517
|
});
|
|
277
518
|
|
|
278
|
-
const sessionId = await proc.createSession("/tmp/project");
|
|
519
|
+
const { sessionId } = await proc.createSession("/tmp/project");
|
|
279
520
|
|
|
280
521
|
expect(sessionId).toBe("session-1");
|
|
281
522
|
expect(newSession).toHaveBeenCalledTimes(1);
|
|
@@ -288,7 +529,7 @@ describe("AcpAgentProcess auth_required retry", () => {
|
|
|
288
529
|
newSessionRejections: [authRequiredError],
|
|
289
530
|
});
|
|
290
531
|
|
|
291
|
-
const sessionId = await proc.createSession("/tmp/project");
|
|
532
|
+
const { sessionId } = await proc.createSession("/tmp/project");
|
|
292
533
|
|
|
293
534
|
expect(sessionId).toBe("session-1");
|
|
294
535
|
expect(authenticate).toHaveBeenCalledTimes(1);
|
|
@@ -387,6 +628,19 @@ describe("AcpAgentProcess auth_required retry", () => {
|
|
|
387
628
|
expect(resumeSession).toHaveBeenCalledTimes(2);
|
|
388
629
|
});
|
|
389
630
|
|
|
631
|
+
test("setConfigOption authenticates and retries on auth_required", async () => {
|
|
632
|
+
const { proc, setSessionConfigOption, authenticate } =
|
|
633
|
+
await setupAuthProcess({
|
|
634
|
+
env: { [OPENAI_VAR]: "sk-test" },
|
|
635
|
+
setConfigOptionRejections: [authRequiredError],
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
await proc.setConfigOption("session-1", "model", "opus");
|
|
639
|
+
|
|
640
|
+
expect(authenticate).toHaveBeenCalledWith({ methodId: "openai-api-key" });
|
|
641
|
+
expect(setSessionConfigOption).toHaveBeenCalledTimes(2);
|
|
642
|
+
});
|
|
643
|
+
|
|
390
644
|
test("prompt authenticates and retries on auth_required, returning the response", async () => {
|
|
391
645
|
const { proc, prompt, authenticate } = await setupAuthProcess({
|
|
392
646
|
env: { [OPENAI_VAR]: "sk-test" },
|