@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
|
@@ -56,7 +56,9 @@ describe("resolveAcpAgent", () => {
|
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
expect(result.agent.command).toBe("codex-acp");
|
|
59
|
-
expect(result.agent.description).toContain(
|
|
59
|
+
expect(result.agent.description).toContain(
|
|
60
|
+
"@agentclientprotocol/codex-acp",
|
|
61
|
+
);
|
|
60
62
|
});
|
|
61
63
|
|
|
62
64
|
test("falls back to default profile for claude when no user entry", () => {
|
|
@@ -191,7 +193,7 @@ describe("resolveAcpAgent", () => {
|
|
|
191
193
|
return;
|
|
192
194
|
}
|
|
193
195
|
expect(result.hint).toBe(
|
|
194
|
-
"bun add -g @agentclientprotocol/claude-agent-acp",
|
|
196
|
+
"bun add -g @agentclientprotocol/claude-agent-acp@0.75.1",
|
|
195
197
|
);
|
|
196
198
|
expect(result.command).toBe("claude-agent-acp");
|
|
197
199
|
});
|
|
@@ -240,7 +242,9 @@ describe("resolveAcpAgent", () => {
|
|
|
240
242
|
if (result.reason !== "binary_not_found") {
|
|
241
243
|
return;
|
|
242
244
|
}
|
|
243
|
-
expect(result.hint).toBe(
|
|
245
|
+
expect(result.hint).toBe(
|
|
246
|
+
"bun add -g @agentclientprotocol/codex-acp@1.10.0",
|
|
247
|
+
);
|
|
244
248
|
});
|
|
245
249
|
|
|
246
250
|
test("binary preflight honors agent.env.PATH override (matches spawn env)", () => {
|
|
@@ -285,6 +289,178 @@ describe("resolveAcpAgent", () => {
|
|
|
285
289
|
expect(result.agent.args).toEqual(["--verbose"]);
|
|
286
290
|
});
|
|
287
291
|
|
|
292
|
+
test("the bundled claude profile carries its model", () => {
|
|
293
|
+
config.setConfig({ agents: {} });
|
|
294
|
+
|
|
295
|
+
const result = resolveAcpAgent("claude");
|
|
296
|
+
|
|
297
|
+
expect(result.ok).toBe(true);
|
|
298
|
+
if (!result.ok) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
expect(result.agent.model).toBe("opus");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("a user entry that names a model overrides the bundled one", () => {
|
|
305
|
+
config.setConfig({
|
|
306
|
+
agents: {
|
|
307
|
+
claude: { command: "claude-agent-acp", args: [], model: "sonnet" },
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const result = resolveAcpAgent("claude");
|
|
312
|
+
|
|
313
|
+
expect(result.ok).toBe(true);
|
|
314
|
+
if (!result.ok) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
expect(result.agent.model).toBe("sonnet");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("a user entry keeping the profile command and omitting model inherits it", () => {
|
|
321
|
+
config.setConfig({
|
|
322
|
+
agents: {
|
|
323
|
+
claude: { command: "claude-agent-acp", args: ["--my-flag"] },
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const result = resolveAcpAgent("claude");
|
|
328
|
+
|
|
329
|
+
expect(result.ok).toBe(true);
|
|
330
|
+
if (!result.ok) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
expect(result.agent.args).toEqual(["--my-flag"]);
|
|
334
|
+
expect(result.agent.model).toBe("opus");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("a user entry that omits command entirely still inherits the model", () => {
|
|
338
|
+
// The schema admits a command-less entry for a bundled id; the profile's
|
|
339
|
+
// command is inherited with the rest.
|
|
340
|
+
config.setConfig({
|
|
341
|
+
agents: {
|
|
342
|
+
claude: JSON.parse('{"args": ["--my-flag"]}'),
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const result = resolveAcpAgent("claude");
|
|
347
|
+
|
|
348
|
+
expect(result.ok).toBe(true);
|
|
349
|
+
if (!result.ok) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
expect(result.agent.command).toBe("claude-agent-acp");
|
|
353
|
+
expect(result.agent.model).toBe("opus");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test("a user entry running the profile adapter by full path still inherits the model", () => {
|
|
357
|
+
config.setConfig({
|
|
358
|
+
agents: {
|
|
359
|
+
claude: { command: "/opt/bin/claude-agent-acp", args: [] },
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const result = resolveAcpAgent("claude");
|
|
364
|
+
|
|
365
|
+
expect(result.ok).toBe(true);
|
|
366
|
+
if (!result.ok) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
expect(result.agent.command).toBe("/opt/bin/claude-agent-acp");
|
|
370
|
+
expect(result.agent.model).toBe("opus");
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test("a user entry pointing the id at another adapter inherits no model", () => {
|
|
374
|
+
// The bundled aliases are Claude's own vocabulary, so an unrelated
|
|
375
|
+
// adapter under the `claude` id must not be handed `opus`.
|
|
376
|
+
config.setConfig({
|
|
377
|
+
agents: {
|
|
378
|
+
claude: { command: "my-custom-claude", args: ["--my-flag"] },
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const result = resolveAcpAgent("claude");
|
|
383
|
+
|
|
384
|
+
expect(result.ok).toBe(true);
|
|
385
|
+
if (!result.ok) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
expect(result.agent.command).toBe("my-custom-claude");
|
|
389
|
+
expect(result.agent.model).toBeUndefined();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("a user entry naming its own model keeps it across a replaced command", () => {
|
|
393
|
+
config.setConfig({
|
|
394
|
+
agents: {
|
|
395
|
+
claude: {
|
|
396
|
+
command: "my-custom-claude",
|
|
397
|
+
args: [],
|
|
398
|
+
model: "my-fork-large",
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const result = resolveAcpAgent("claude");
|
|
404
|
+
|
|
405
|
+
expect(result.ok).toBe(true);
|
|
406
|
+
if (!result.ok) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
expect(result.agent.command).toBe("my-custom-claude");
|
|
410
|
+
expect(result.agent.model).toBe("my-fork-large");
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test("a user entry pointing the id at another adapter inherits no description", () => {
|
|
414
|
+
config.setConfig({
|
|
415
|
+
agents: {
|
|
416
|
+
claude: { command: "my-custom-claude", args: [] },
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const result = resolveAcpAgent("claude");
|
|
421
|
+
|
|
422
|
+
expect(result.ok).toBe(true);
|
|
423
|
+
if (!result.ok) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
expect(result.agent.description).toBeUndefined();
|
|
427
|
+
const listed = listAcpAgents().agents.find(
|
|
428
|
+
(entry) => entry.id === "claude",
|
|
429
|
+
);
|
|
430
|
+
expect(listed?.command).toBe("my-custom-claude");
|
|
431
|
+
expect(listed?.description).toBeUndefined();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("a user entry running the profile adapter by full path inherits its description", () => {
|
|
435
|
+
config.setConfig({
|
|
436
|
+
agents: {
|
|
437
|
+
claude: { command: "/opt/bin/claude-agent-acp", args: [] },
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const result = resolveAcpAgent("claude");
|
|
442
|
+
|
|
443
|
+
expect(result.ok).toBe(true);
|
|
444
|
+
if (!result.ok) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
expect(result.agent.description).toContain(
|
|
448
|
+
"@agentclientprotocol/claude-agent-acp",
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("a bundled profile with no model leaves the resolved agent without one", () => {
|
|
453
|
+
config.setConfig({ agents: {} });
|
|
454
|
+
|
|
455
|
+
const result = resolveAcpAgent("codex");
|
|
456
|
+
|
|
457
|
+
expect(result.ok).toBe(true);
|
|
458
|
+
if (!result.ok) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
expect(result.agent.model).toBeUndefined();
|
|
462
|
+
});
|
|
463
|
+
|
|
288
464
|
test("resolves a full-path command directly without rewriting it", () => {
|
|
289
465
|
config.setConfig({
|
|
290
466
|
agents: {
|
|
@@ -332,7 +508,7 @@ describe("resolveAcpAgent - missing binary", () => {
|
|
|
332
508
|
}
|
|
333
509
|
expect(result.command).toBe("claude-agent-acp");
|
|
334
510
|
expect(result.hint).toBe(
|
|
335
|
-
"bun add -g @agentclientprotocol/claude-agent-acp",
|
|
511
|
+
"bun add -g @agentclientprotocol/claude-agent-acp@0.75.1",
|
|
336
512
|
);
|
|
337
513
|
});
|
|
338
514
|
|
|
@@ -351,7 +527,7 @@ describe("resolveAcpAgent - missing binary", () => {
|
|
|
351
527
|
return;
|
|
352
528
|
}
|
|
353
529
|
expect(result.hint).toBe(
|
|
354
|
-
"bun add -g @agentclientprotocol/claude-agent-acp",
|
|
530
|
+
"bun add -g @agentclientprotocol/claude-agent-acp@0.75.1",
|
|
355
531
|
);
|
|
356
532
|
});
|
|
357
533
|
});
|
|
@@ -430,7 +606,9 @@ describe("listAcpAgents", () => {
|
|
|
430
606
|
const codex = result.agents.find((a) => a.id === "codex");
|
|
431
607
|
expect(codex?.available).toBe(false);
|
|
432
608
|
expect(codex?.unavailableReason).toBe("'codex-acp' is not on PATH");
|
|
433
|
-
expect(codex?.setupHint).toBe(
|
|
609
|
+
expect(codex?.setupHint).toBe(
|
|
610
|
+
"bun add -g @agentclientprotocol/codex-acp@1.10.0",
|
|
611
|
+
);
|
|
434
612
|
});
|
|
435
613
|
|
|
436
614
|
test("aliases are resolution sugar, not catalog entries", () => {
|
|
@@ -489,3 +667,39 @@ describe("listAcpAgents", () => {
|
|
|
489
667
|
);
|
|
490
668
|
});
|
|
491
669
|
});
|
|
670
|
+
|
|
671
|
+
describe("agent ids that name Object.prototype members", () => {
|
|
672
|
+
// An id read off the prototype chain would resolve as a configured agent
|
|
673
|
+
// that does not exist, and a spawn would start a process for a config
|
|
674
|
+
// nobody wrote.
|
|
675
|
+
for (const id of ["__proto__", "constructor", "prototype"]) {
|
|
676
|
+
test(`"${id}" resolves to nothing`, () => {
|
|
677
|
+
config.setConfig({ agents: {} });
|
|
678
|
+
|
|
679
|
+
const resolved = resolveAcpAgent(id);
|
|
680
|
+
expect(resolved.ok).toBe(false);
|
|
681
|
+
if (resolved.ok) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
expect(resolved.reason).toBe("unknown_agent");
|
|
685
|
+
if (resolved.reason !== "unknown_agent") {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
expect(resolved.available).toEqual(["claude", "codex"]);
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
test("the catalog never lists one, even when config carries the key", () => {
|
|
693
|
+
config.setConfig({
|
|
694
|
+
agents: {
|
|
695
|
+
...JSON.parse('{"__proto__": {"command": "evil", "args": []}}'),
|
|
696
|
+
claude: { command: "claude-agent-acp", args: [] },
|
|
697
|
+
},
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
expect(listAcpAgents().agents.map((a) => a.id)).toEqual([
|
|
701
|
+
"claude",
|
|
702
|
+
"codex",
|
|
703
|
+
]);
|
|
704
|
+
});
|
|
705
|
+
});
|
package/src/acp/resolve-agent.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared resolver for ACP agent ids → agent config + binary preflight.
|
|
3
3
|
*
|
|
4
|
-
* `resolveAcpAgent(id)`
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
* names ("claude code", "OpenAI Codex")
|
|
8
|
-
* raw id misses both maps. The result
|
|
9
|
-
* reason a spawn might fail before we
|
|
10
|
-
* agent id, or binary missing from
|
|
11
|
-
* and the `/v1/acp/spawn` HTTP
|
|
12
|
-
* matching actionable hints.
|
|
4
|
+
* `resolveAcpAgent(id)` layers user-provided `config.acp.agents[id]` over the
|
|
5
|
+
* bundled `DEFAULT_ACP_AGENT_PROFILES` so common agents like `claude` and
|
|
6
|
+
* `codex` Just Work with no per-user config required; `mergeWithProfile` says
|
|
7
|
+
* what an entry inherits. Natural names ("claude code", "OpenAI Codex")
|
|
8
|
+
* resolve via `AGENT_ID_ALIASES` when the raw id misses both maps. The result
|
|
9
|
+
* is a discriminated union covering every reason a spawn might fail before we
|
|
10
|
+
* even start the agent process: unknown agent id, or binary missing from
|
|
11
|
+
* PATH. Callers (acp_spawn, acp_list_agents, and the `/v1/acp/spawn` HTTP
|
|
12
|
+
* route) get a single source of truth and matching actionable hints.
|
|
13
13
|
*
|
|
14
14
|
* The resolver NEVER fetches or runs packages in the (untrusted) task cwd.
|
|
15
15
|
* When the adapter binary is missing, resolution simply fails with
|
|
@@ -25,12 +25,16 @@
|
|
|
25
25
|
* `available` / `setupHint` derived from the same binary resolution.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import { basename } from "node:path";
|
|
29
|
+
|
|
28
30
|
import {
|
|
31
|
+
type AcpAgentProfile,
|
|
29
32
|
DEFAULT_ACP_AGENT_PROFILES,
|
|
30
33
|
DEFAULT_AGENT_NPM_PACKAGES,
|
|
31
34
|
} from "../config/acp-defaults.js";
|
|
32
|
-
import type { AcpAgentConfig } from "../config/acp-schema.js";
|
|
35
|
+
import type { AcpAgentConfig as ConfiguredAcpAgent } from "../config/acp-schema.js";
|
|
33
36
|
import { getConfig } from "../config/loader.js";
|
|
37
|
+
import type { AcpAgentConfig } from "./types.js";
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
40
|
* Whether this agent's entry came from user config (wins over default) or
|
|
@@ -134,6 +138,19 @@ const AGENT_ID_ALIASES: Record<string, string> = {
|
|
|
134
138
|
openaicodex: "codex",
|
|
135
139
|
};
|
|
136
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Ids that read back as inherited `Object.prototype` members instead of
|
|
143
|
+
* configured agents. `directLookup` and `mergedAgentIds` refuse them so an id
|
|
144
|
+
* from a tool call or an HTTP body can resolve to nothing but a real entry:
|
|
145
|
+
* resolving one would report a configured agent that does not exist, and a
|
|
146
|
+
* spawn would then start a process for a config nobody wrote.
|
|
147
|
+
*/
|
|
148
|
+
const PROTOTYPE_SENSITIVE_AGENT_IDS: ReadonlySet<string> = new Set([
|
|
149
|
+
"__proto__",
|
|
150
|
+
"constructor",
|
|
151
|
+
"prototype",
|
|
152
|
+
]);
|
|
153
|
+
|
|
137
154
|
/**
|
|
138
155
|
* Normalize a raw agent id for alias matching: lowercase and strip spaces,
|
|
139
156
|
* underscores, and hyphens so "Claude Code", "claude-code", and
|
|
@@ -166,45 +183,92 @@ export function lookupAcpAgentConfig(id: string): AcpAgentConfig | undefined {
|
|
|
166
183
|
}
|
|
167
184
|
|
|
168
185
|
function lookupAgent(
|
|
169
|
-
userAgents: Record<string,
|
|
186
|
+
userAgents: Record<string, ConfiguredAcpAgent>,
|
|
170
187
|
id: string,
|
|
171
|
-
): { agent: AcpAgentConfig; source: AcpAgentSource } | undefined {
|
|
188
|
+
): { agent: AcpAgentConfig; source: AcpAgentSource; id: string } | undefined {
|
|
172
189
|
const direct = directLookup(userAgents, id);
|
|
173
190
|
if (direct) {
|
|
174
|
-
return direct;
|
|
191
|
+
return { ...direct, id };
|
|
175
192
|
}
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
?
|
|
193
|
+
const normalized = normalizeAgentId(id);
|
|
194
|
+
const canonicalId = Object.hasOwn(AGENT_ID_ALIASES, normalized)
|
|
195
|
+
? AGENT_ID_ALIASES[normalized]
|
|
179
196
|
: undefined;
|
|
197
|
+
if (canonicalId === undefined) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
const aliased = directLookup(userAgents, canonicalId);
|
|
201
|
+
return aliased ? { ...aliased, id: canonicalId } : undefined;
|
|
180
202
|
}
|
|
181
203
|
|
|
182
204
|
function directLookup(
|
|
183
|
-
userAgents: Record<string,
|
|
205
|
+
userAgents: Record<string, ConfiguredAcpAgent>,
|
|
184
206
|
id: string,
|
|
185
207
|
): { agent: AcpAgentConfig; source: AcpAgentSource } | undefined {
|
|
186
|
-
|
|
208
|
+
if (PROTOTYPE_SENSITIVE_AGENT_IDS.has(id)) {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
const userAgent = Object.hasOwn(userAgents, id) ? userAgents[id] : undefined;
|
|
212
|
+
const defaultAgent = Object.hasOwn(DEFAULT_ACP_AGENT_PROFILES, id)
|
|
213
|
+
? DEFAULT_ACP_AGENT_PROFILES[id]
|
|
214
|
+
: undefined;
|
|
187
215
|
if (userAgent) {
|
|
188
|
-
|
|
216
|
+
const agent = mergeWithProfile(userAgent, defaultAgent);
|
|
217
|
+
return agent ? { agent, source: "config" } : undefined;
|
|
189
218
|
}
|
|
190
|
-
const defaultAgent = DEFAULT_ACP_AGENT_PROFILES[id];
|
|
191
219
|
if (defaultAgent) {
|
|
192
220
|
return { agent: defaultAgent, source: "default" };
|
|
193
221
|
}
|
|
194
222
|
return undefined;
|
|
195
223
|
}
|
|
196
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Fill a user config entry from the bundled profile for the same id. An entry
|
|
227
|
+
* that runs the profile's adapter (it omits `command`, or names the same
|
|
228
|
+
* binary, by full path or not) inherits the leaves it leaves out: `command`
|
|
229
|
+
* itself, the description, and the `model` a session starts on. `args` is the
|
|
230
|
+
* exception, because the schema defaults it to `[]`: a parsed entry always
|
|
231
|
+
* carries its own, so the profile's never reaches it. An entry that points
|
|
232
|
+
* the id at a different adapter stands alone, like any user-only entry,
|
|
233
|
+
* so a config that reuses the `claude` id for something else never carries
|
|
234
|
+
* Claude's description or has Claude's `opus` sent to it. Zod omits absent
|
|
235
|
+
* optional keys, so an omission never spreads as an undefined override.
|
|
236
|
+
*
|
|
237
|
+
* `undefined` only for an entry with neither a command nor a profile to take
|
|
238
|
+
* one from, a shape the config schema rejects before it gets here.
|
|
239
|
+
*/
|
|
240
|
+
function mergeWithProfile(
|
|
241
|
+
userAgent: ConfiguredAcpAgent,
|
|
242
|
+
profile: AcpAgentProfile | undefined,
|
|
243
|
+
): AcpAgentConfig | undefined {
|
|
244
|
+
const command = userAgent.command;
|
|
245
|
+
if (!profile) {
|
|
246
|
+
return command === undefined ? undefined : { ...userAgent, command };
|
|
247
|
+
}
|
|
248
|
+
// A full path to the bundled binary is still the bundled adapter; the
|
|
249
|
+
// basename is the adapter's identity everywhere else in this module.
|
|
250
|
+
if (
|
|
251
|
+
command !== undefined &&
|
|
252
|
+
basename(command) !== basename(profile.command)
|
|
253
|
+
) {
|
|
254
|
+
return { ...userAgent, command };
|
|
255
|
+
}
|
|
256
|
+
return { ...profile, ...userAgent, command: command ?? profile.command };
|
|
257
|
+
}
|
|
258
|
+
|
|
197
259
|
/**
|
|
198
260
|
* Defaults first (declaration order), then user-only ids. Deduplicated so a
|
|
199
261
|
* user config that overrides a default doesn't list the id twice.
|
|
200
262
|
*/
|
|
201
|
-
function mergedAgentIds(
|
|
263
|
+
function mergedAgentIds(
|
|
264
|
+
userAgents: Record<string, ConfiguredAcpAgent>,
|
|
265
|
+
): string[] {
|
|
202
266
|
return Array.from(
|
|
203
267
|
new Set([
|
|
204
268
|
...Object.keys(DEFAULT_ACP_AGENT_PROFILES),
|
|
205
269
|
...Object.keys(userAgents),
|
|
206
270
|
]),
|
|
207
|
-
);
|
|
271
|
+
).filter((id) => !PROTOTYPE_SENSITIVE_AGENT_IDS.has(id));
|
|
208
272
|
}
|
|
209
273
|
|
|
210
274
|
/**
|
|
@@ -256,7 +320,8 @@ export function listAcpAgents(): {
|
|
|
256
320
|
const config = getConfig();
|
|
257
321
|
const userAgents = config.acp.agents;
|
|
258
322
|
const agents: AcpAgentEntry[] = mergedAgentIds(userAgents).map((id) => {
|
|
259
|
-
// Non-null: ids come from `mergedAgentIds
|
|
323
|
+
// Non-null: ids come from `mergedAgentIds`, and the schema admits a
|
|
324
|
+
// command-less entry only for a bundled id, so the lookup always resolves.
|
|
260
325
|
const { agent, source } = lookupAgent(userAgents, id)!;
|
|
261
326
|
// Same binary preflight as `resolveAcpAgent`: available iff the command
|
|
262
327
|
// is on PATH. A missing binary is auto-installed at spawn time, but the
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import { RequestError } from "@agentclientprotocol/sdk";
|
|
4
|
+
|
|
3
5
|
import type { Conversation } from "../daemon/conversation.js";
|
|
4
6
|
import {
|
|
5
7
|
deleteConversation,
|
|
@@ -39,6 +41,7 @@ mock.module("./prepare-agent-env.js", () => ({
|
|
|
39
41
|
}));
|
|
40
42
|
|
|
41
43
|
import { createAbortReason } from "../util/abort-reasons.js";
|
|
44
|
+
import { modelOption } from "./__tests__/helpers/acp-model-option.js";
|
|
42
45
|
import { VellumAcpClientHandler } from "./client-handler.js";
|
|
43
46
|
import { AcpSessionManager } from "./session-manager.js";
|
|
44
47
|
|
|
@@ -90,10 +93,10 @@ function mockConversation(opts?: { enqueueQueued?: boolean }) {
|
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
/** Fake AcpAgentProcess covering only the calls firePromptInBackground makes. */
|
|
93
|
-
function fakeProcess(prompt: () => Promise<unknown
|
|
96
|
+
function fakeProcess(prompt: () => Promise<unknown>, stderr = "") {
|
|
94
97
|
return {
|
|
95
98
|
markStderr: () => 0,
|
|
96
|
-
stderrSince: () =>
|
|
99
|
+
stderrSince: () => stderr,
|
|
97
100
|
prompt,
|
|
98
101
|
kill: mock(() => {}),
|
|
99
102
|
};
|
|
@@ -365,6 +368,8 @@ describe("AcpSessionManager auth-required recovery surface", () => {
|
|
|
365
368
|
parentToolUseId?: string;
|
|
366
369
|
cancelled?: boolean;
|
|
367
370
|
credentialDigest?: string;
|
|
371
|
+
failure?: () => Promise<never>;
|
|
372
|
+
stderr?: string;
|
|
368
373
|
}) {
|
|
369
374
|
const manager = new AcpSessionManager(1);
|
|
370
375
|
const parentId = `parent-${opts.id}`;
|
|
@@ -376,7 +381,7 @@ describe("AcpSessionManager auth-required recovery surface", () => {
|
|
|
376
381
|
manager,
|
|
377
382
|
opts.id,
|
|
378
383
|
parentId,
|
|
379
|
-
fakeProcess(authFailure),
|
|
384
|
+
fakeProcess(opts.failure ?? authFailure, opts.stderr),
|
|
380
385
|
);
|
|
381
386
|
entry.command = opts.command;
|
|
382
387
|
(entry as { parentToolUseId?: string }).parentToolUseId =
|
|
@@ -512,6 +517,62 @@ describe("AcpSessionManager auth-required recovery surface", () => {
|
|
|
512
517
|
expect(r.persistedContent).toBeUndefined();
|
|
513
518
|
});
|
|
514
519
|
|
|
520
|
+
test("claude's prompt-time 401 raises the surface when stderr names another failure", async () => {
|
|
521
|
+
// claude-agent-acp raises it as RequestError.internalError({ errorKind },
|
|
522
|
+
// cliText), and stderr supplies the failure message, so the auth text
|
|
523
|
+
// survives only on the rejection's own message.
|
|
524
|
+
refusedDigests.length = 0;
|
|
525
|
+
const digest = claudeTokenDigest("sk-ant-oat-prompt-401");
|
|
526
|
+
const r = await driveAuthFailure({
|
|
527
|
+
id: "sess-auth-prompt-401",
|
|
528
|
+
command: "claude-agent-acp",
|
|
529
|
+
parentToolUseId: "tool-anchor-prompt-401",
|
|
530
|
+
credentialDigest: digest,
|
|
531
|
+
failure: () =>
|
|
532
|
+
Promise.reject(
|
|
533
|
+
new RequestError(
|
|
534
|
+
-32603,
|
|
535
|
+
"Internal error: Failed to authenticate. API Error: 401 OAuth access token has expired.",
|
|
536
|
+
{ errorKind: "authentication_failed" },
|
|
537
|
+
),
|
|
538
|
+
),
|
|
539
|
+
stderr: '{"error":{"message":"Something else happened"}}',
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
expect(r.authEvent).toMatchObject({
|
|
543
|
+
acpSessionId: "sess-auth-prompt-401",
|
|
544
|
+
authCode: "acp_claude_auth_required",
|
|
545
|
+
parentToolUseId: "tool-anchor-prompt-401",
|
|
546
|
+
});
|
|
547
|
+
expect(hasAcpConnectCardRaised(r.parentId)).toBe(true);
|
|
548
|
+
expect(refusedDigests).toContain(digest);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("the rejection's own message is checked when its payload names a different reason", async () => {
|
|
552
|
+
refusedDigests.length = 0;
|
|
553
|
+
const digest = claudeTokenDigest("sk-ant-oat-own-message");
|
|
554
|
+
const r = await driveAuthFailure({
|
|
555
|
+
id: "sess-auth-own-message",
|
|
556
|
+
command: "claude-agent-acp",
|
|
557
|
+
parentToolUseId: "tool-anchor-own-message",
|
|
558
|
+
credentialDigest: digest,
|
|
559
|
+
failure: () =>
|
|
560
|
+
Promise.reject(
|
|
561
|
+
new RequestError(
|
|
562
|
+
-32603,
|
|
563
|
+
"Internal error: Failed to authenticate. API Error: 401",
|
|
564
|
+
{ details: "Request failed" },
|
|
565
|
+
),
|
|
566
|
+
),
|
|
567
|
+
stderr: '{"error":{"message":"Something else happened"}}',
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
expect(r.authEvent).toMatchObject({
|
|
571
|
+
authCode: "acp_claude_auth_required",
|
|
572
|
+
});
|
|
573
|
+
expect(refusedDigests).toContain(digest);
|
|
574
|
+
});
|
|
575
|
+
|
|
515
576
|
test("a non-claude adapter never raises the surface, even on an auth-shaped failure", async () => {
|
|
516
577
|
const r = await driveAuthFailure({
|
|
517
578
|
id: "sess-auth-codex",
|
|
@@ -716,4 +777,136 @@ describe("AcpSessionManager.spawn: a stopped turn leaves no agent running", () =
|
|
|
716
777
|
expect(proc.kill).toHaveBeenCalled();
|
|
717
778
|
expect(internals.sessions.size).toBe(0);
|
|
718
779
|
});
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* The model pin is an await of its own, and it sits after the setup
|
|
783
|
+
* recheck. A stop landing inside it gets the same treatment: no spawned
|
|
784
|
+
* event, no model event, no prompt, and no session left behind.
|
|
785
|
+
*/
|
|
786
|
+
test("a cancel during the model pin tears the process down and fires nothing", async () => {
|
|
787
|
+
const manager = new AcpSessionManager(1);
|
|
788
|
+
const controller = new AbortController();
|
|
789
|
+
const prompt = mock(() => Promise.resolve({}));
|
|
790
|
+
const setConfigOption = mock(async () => {
|
|
791
|
+
// The user stops the turn while the pin's round trip is open.
|
|
792
|
+
controller.abort(REASON);
|
|
793
|
+
return [modelOption("opus")];
|
|
794
|
+
});
|
|
795
|
+
const proc = {
|
|
796
|
+
...fakeProcess(prompt),
|
|
797
|
+
spawn: () => {},
|
|
798
|
+
initialize: async () => {},
|
|
799
|
+
createSession: async () => ({
|
|
800
|
+
sessionId: "proto-1",
|
|
801
|
+
configOptions: [modelOption("sonnet")],
|
|
802
|
+
}),
|
|
803
|
+
setConfigOption,
|
|
804
|
+
};
|
|
805
|
+
let injected: ReturnType<typeof injectSession> | undefined;
|
|
806
|
+
const internals = manager as unknown as {
|
|
807
|
+
registerSession: (opts: {
|
|
808
|
+
acpSessionId: string;
|
|
809
|
+
parentConversationId: string;
|
|
810
|
+
}) => unknown;
|
|
811
|
+
sessions: Map<string, unknown>;
|
|
812
|
+
};
|
|
813
|
+
internals.registerSession = (opts) => {
|
|
814
|
+
injected = injectSession(
|
|
815
|
+
manager,
|
|
816
|
+
opts.acpSessionId,
|
|
817
|
+
opts.parentConversationId,
|
|
818
|
+
proc as unknown as ReturnType<typeof fakeProcess>,
|
|
819
|
+
);
|
|
820
|
+
return injected;
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
await expect(
|
|
824
|
+
manager.spawn(
|
|
825
|
+
"claude",
|
|
826
|
+
{ command: "noop", args: [] } as never,
|
|
827
|
+
"do the task",
|
|
828
|
+
"/tmp",
|
|
829
|
+
"conv-1",
|
|
830
|
+
() => {},
|
|
831
|
+
{ model: "opus" },
|
|
832
|
+
{ signal: controller.signal },
|
|
833
|
+
),
|
|
834
|
+
).rejects.toThrow();
|
|
835
|
+
|
|
836
|
+
expect(setConfigOption).toHaveBeenCalled();
|
|
837
|
+
expect(injected?.sendToVellum).not.toHaveBeenCalled();
|
|
838
|
+
expect(prompt).not.toHaveBeenCalled();
|
|
839
|
+
expect(proc.kill).toHaveBeenCalled();
|
|
840
|
+
expect(internals.sessions.size).toBe(0);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* A cancel that persists a resumable row frees the id, so a resume can
|
|
845
|
+
* register a fresh entry under it before the stopped spawn's pin settles.
|
|
846
|
+
* The teardown owes that spawn its own process, and owes the replacement
|
|
847
|
+
* its map slot.
|
|
848
|
+
*/
|
|
849
|
+
test("a cancel during the model pin leaves a replacement session for the same id alone", async () => {
|
|
850
|
+
const manager = new AcpSessionManager(2);
|
|
851
|
+
const controller = new AbortController();
|
|
852
|
+
const prompt = mock(() => Promise.resolve({}));
|
|
853
|
+
let replacement: ReturnType<typeof injectSession> | undefined;
|
|
854
|
+
let spawnedId: string | undefined;
|
|
855
|
+
const setConfigOption = mock(async () => {
|
|
856
|
+
// The turn is stopped and the id is resumed by another request while
|
|
857
|
+
// the pin's round trip is still open.
|
|
858
|
+
controller.abort(REASON);
|
|
859
|
+
replacement = injectSession(
|
|
860
|
+
manager,
|
|
861
|
+
spawnedId!,
|
|
862
|
+
"conv-2",
|
|
863
|
+
fakeProcess(mock(() => Promise.resolve({}))),
|
|
864
|
+
);
|
|
865
|
+
return [modelOption("opus")];
|
|
866
|
+
});
|
|
867
|
+
const proc = {
|
|
868
|
+
...fakeProcess(prompt),
|
|
869
|
+
spawn: () => {},
|
|
870
|
+
initialize: async () => {},
|
|
871
|
+
createSession: async () => ({
|
|
872
|
+
sessionId: "proto-1",
|
|
873
|
+
configOptions: [modelOption("sonnet")],
|
|
874
|
+
}),
|
|
875
|
+
setConfigOption,
|
|
876
|
+
};
|
|
877
|
+
const internals = manager as unknown as {
|
|
878
|
+
registerSession: (opts: {
|
|
879
|
+
acpSessionId: string;
|
|
880
|
+
parentConversationId: string;
|
|
881
|
+
}) => unknown;
|
|
882
|
+
sessions: Map<string, unknown>;
|
|
883
|
+
};
|
|
884
|
+
internals.registerSession = (opts) => {
|
|
885
|
+
spawnedId = opts.acpSessionId;
|
|
886
|
+
return injectSession(
|
|
887
|
+
manager,
|
|
888
|
+
opts.acpSessionId,
|
|
889
|
+
opts.parentConversationId,
|
|
890
|
+
proc as unknown as ReturnType<typeof fakeProcess>,
|
|
891
|
+
);
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
await expect(
|
|
895
|
+
manager.spawn(
|
|
896
|
+
"claude",
|
|
897
|
+
{ command: "noop", args: [] } as never,
|
|
898
|
+
"do the task",
|
|
899
|
+
"/tmp",
|
|
900
|
+
"conv-1",
|
|
901
|
+
() => {},
|
|
902
|
+
{ model: "opus" },
|
|
903
|
+
{ signal: controller.signal },
|
|
904
|
+
),
|
|
905
|
+
).rejects.toThrow();
|
|
906
|
+
|
|
907
|
+
expect(prompt).not.toHaveBeenCalled();
|
|
908
|
+
expect(proc.kill).toHaveBeenCalled();
|
|
909
|
+
expect(replacement?.process.kill).not.toHaveBeenCalled();
|
|
910
|
+
expect(internals.sessions.get(spawnedId!)).toBe(replacement);
|
|
911
|
+
});
|
|
719
912
|
});
|