@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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { basename } from "node:path";
|
|
8
8
|
|
|
9
|
+
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
|
|
9
10
|
import { eq, inArray } from "drizzle-orm";
|
|
10
11
|
|
|
11
12
|
import type { AcpSessionUpdateEvent } from "../api/events/acp-session-update.js";
|
|
@@ -26,17 +27,27 @@ import { AcpAgentProcess } from "./agent-process.js";
|
|
|
26
27
|
import {
|
|
27
28
|
ACP_AUTH_RECOVERY_GUIDANCE,
|
|
28
29
|
ACP_CLAUDE_AUTH_REQUIRED_CODE,
|
|
30
|
+
AcpAuthRequiredError,
|
|
29
31
|
CLAUDE_ACP_COMMAND,
|
|
30
32
|
isAcpAuthRequired,
|
|
31
33
|
isClaudeAuthFailureMessage,
|
|
34
|
+
requestErrorReason,
|
|
32
35
|
} from "./auth-required.js";
|
|
33
36
|
import { resolveAgentWithAutoInstall } from "./auto-install.js";
|
|
34
37
|
import { VellumAcpClientHandler } from "./client-handler.js";
|
|
35
38
|
import { deriveFailureError } from "./failure-error.js";
|
|
39
|
+
import type { AcpModelInfo } from "./model-config.js";
|
|
40
|
+
import { deriveModelInfo, resolveAcpModel } from "./model-config.js";
|
|
36
41
|
import { prepareAgentEnv } from "./prepare-agent-env.js";
|
|
37
42
|
import { formatResolveFailure } from "./resolve-agent.js";
|
|
38
43
|
import { claudeResumeHint } from "./resume-hint.js";
|
|
39
|
-
import
|
|
44
|
+
import {
|
|
45
|
+
ACP_LIVE_STATUSES,
|
|
46
|
+
type AcpAgentConfig,
|
|
47
|
+
AcpConfigOptionRefusedError,
|
|
48
|
+
type AcpSessionState,
|
|
49
|
+
isLiveAcpStatus,
|
|
50
|
+
} from "./types.js";
|
|
40
51
|
|
|
41
52
|
const log = getLogger("acp:session-manager");
|
|
42
53
|
|
|
@@ -46,7 +57,9 @@ const log = getLogger("acp:session-manager");
|
|
|
46
57
|
* and the marker promises a repair only the Connect Claude flow can perform.
|
|
47
58
|
* Checks both auth-failure shapes (see `auth-required.ts`) against both the
|
|
48
59
|
* raw rejection and the derived failure message, since `deriveFailureError`
|
|
49
|
-
* may replace one with the other.
|
|
60
|
+
* may replace one with the other. The raw side reads both the rejection's own
|
|
61
|
+
* message and its decoded reason, because an adapter's words travel in either
|
|
62
|
+
* depending on how it raised the error.
|
|
50
63
|
*/
|
|
51
64
|
function claudeAuthRequiredCode(
|
|
52
65
|
err: unknown,
|
|
@@ -59,6 +72,7 @@ function claudeAuthRequiredCode(
|
|
|
59
72
|
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
60
73
|
return isAcpAuthRequired(err) ||
|
|
61
74
|
isClaudeAuthFailureMessage(rawMessage) ||
|
|
75
|
+
isClaudeAuthFailureMessage(requestErrorReason(err)) ||
|
|
62
76
|
isClaudeAuthFailureMessage(failureMessage)
|
|
63
77
|
? ACP_CLAUDE_AUTH_REQUIRED_CODE
|
|
64
78
|
: undefined;
|
|
@@ -125,6 +139,28 @@ interface SessionEntry {
|
|
|
125
139
|
* rather than needing a sweep to retire it. Absent for agents that use no
|
|
126
140
|
* Claude credential. */
|
|
127
141
|
credentialDigest?: string;
|
|
142
|
+
/** Config id of the adapter's model selector, absent when it advertises
|
|
143
|
+
* none. Both the id to write a model back through and the flag that says
|
|
144
|
+
* this session has a model to publish at all. */
|
|
145
|
+
modelConfigId?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** What a spawn or resume pin did about the model it was asked for. */
|
|
149
|
+
interface ModelPinResult {
|
|
150
|
+
/** False when the session is not on the model asked for: the adapter
|
|
151
|
+
* refused it, or advertises no selector at all. */
|
|
152
|
+
applied: boolean;
|
|
153
|
+
/** Message to relay, present only when the caller named the model. */
|
|
154
|
+
warning?: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The sentence a caller who named a model gets when the session has no model
|
|
159
|
+
* selector to put it through, whether the adapter never advertised one or its
|
|
160
|
+
* answer to the pin dropped the one it had.
|
|
161
|
+
*/
|
|
162
|
+
function noModelSelectionWarning(agentId: string): string {
|
|
163
|
+
return `Agent "${agentId}" does not support model selection, so the session is running on the agent's own model.`;
|
|
128
164
|
}
|
|
129
165
|
|
|
130
166
|
/**
|
|
@@ -149,6 +185,10 @@ export interface AcpCancellationOptions {
|
|
|
149
185
|
|
|
150
186
|
export class AcpSessionManager {
|
|
151
187
|
private sessions = new Map<string, SessionEntry>();
|
|
188
|
+
/** Scopes process-local model revisions across assistant restarts. */
|
|
189
|
+
private readonly modelRevisionEpoch = randomUUID();
|
|
190
|
+
/** Orders model snapshots within this assistant process. */
|
|
191
|
+
private modelRevision = 0;
|
|
152
192
|
/**
|
|
153
193
|
* Per-session ring buffer of wire-shaped update events forwarded to
|
|
154
194
|
* clients. Bounded by event count and aggregate JSON byte size; oldest
|
|
@@ -201,7 +241,7 @@ export class AcpSessionManager {
|
|
|
201
241
|
stopReason: "daemon_restarted",
|
|
202
242
|
completedAt: Date.now(),
|
|
203
243
|
})
|
|
204
|
-
.where(inArray(acpSessionHistory.status, [
|
|
244
|
+
.where(inArray(acpSessionHistory.status, [...ACP_LIVE_STATUSES]))
|
|
205
245
|
.run();
|
|
206
246
|
} catch (err) {
|
|
207
247
|
log.error(
|
|
@@ -243,6 +283,12 @@ export class AcpSessionManager {
|
|
|
243
283
|
*
|
|
244
284
|
* The prompt is fired in the background — results stream via sessionUpdate
|
|
245
285
|
* callbacks and completion/error messages are sent when the prompt finishes.
|
|
286
|
+
*
|
|
287
|
+
* `requestedModel` is the normalized explicit request this spawn acted on.
|
|
288
|
+
* `effectiveModel` is the model the adapter reports for the live session.
|
|
289
|
+
* `modelWarning` comes back when the adapter refused the model the session
|
|
290
|
+
* was asked for: the caller relays the reason rather than treating the spawn
|
|
291
|
+
* as failed.
|
|
246
292
|
*/
|
|
247
293
|
async spawn(
|
|
248
294
|
agentId: string,
|
|
@@ -251,11 +297,17 @@ export class AcpSessionManager {
|
|
|
251
297
|
cwd: string,
|
|
252
298
|
parentConversationId: string,
|
|
253
299
|
sendToVellum: (msg: AssistantEvent) => void,
|
|
254
|
-
parentToolUseId?: string,
|
|
255
|
-
|
|
256
|
-
): Promise<{
|
|
300
|
+
options?: { parentToolUseId?: string; model?: string },
|
|
301
|
+
cancellation?: AcpCancellationOptions,
|
|
302
|
+
): Promise<{
|
|
303
|
+
acpSessionId: string;
|
|
304
|
+
protocolSessionId: string;
|
|
305
|
+
requestedModel?: string;
|
|
306
|
+
effectiveModel?: string;
|
|
307
|
+
modelWarning?: string;
|
|
308
|
+
}> {
|
|
257
309
|
this.assertCapacity();
|
|
258
|
-
|
|
310
|
+
cancellation?.signal?.throwIfAborted();
|
|
259
311
|
|
|
260
312
|
const acpSessionId = randomUUID();
|
|
261
313
|
log.info(
|
|
@@ -277,11 +329,20 @@ export class AcpSessionManager {
|
|
|
277
329
|
cwd,
|
|
278
330
|
startedAt: Date.now(),
|
|
279
331
|
sendToVellum,
|
|
280
|
-
parentToolUseId,
|
|
332
|
+
parentToolUseId: options?.parentToolUseId,
|
|
281
333
|
task,
|
|
282
334
|
});
|
|
283
335
|
const { process: agentProcess, state } = entry;
|
|
284
336
|
|
|
337
|
+
const requestedModel = options?.model?.trim() || undefined;
|
|
338
|
+
// Resolved before the adapter is asked for anything, so the ladder is
|
|
339
|
+
// walked once against the config the spawn was made under.
|
|
340
|
+
const resolvedModel = resolveAcpModel({
|
|
341
|
+
requestedModel,
|
|
342
|
+
agentModel: agentConfig.model,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
let configOptions: SessionConfigOption[] = [];
|
|
285
346
|
try {
|
|
286
347
|
log.info({ acpSessionId, agentId }, "ACP spawning child process");
|
|
287
348
|
agentProcess.spawn(cwd);
|
|
@@ -291,7 +352,9 @@ export class AcpSessionManager {
|
|
|
291
352
|
);
|
|
292
353
|
await agentProcess.initialize();
|
|
293
354
|
log.info({ acpSessionId, agentId }, "ACP creating session");
|
|
294
|
-
const
|
|
355
|
+
const created = await agentProcess.createSession(cwd);
|
|
356
|
+
const acpProtocolSessionId = created.sessionId;
|
|
357
|
+
configOptions = created.configOptions;
|
|
295
358
|
state.acpSessionId = acpProtocolSessionId;
|
|
296
359
|
state.status = "running";
|
|
297
360
|
log.info(
|
|
@@ -317,16 +380,47 @@ export class AcpSessionManager {
|
|
|
317
380
|
// awaits, and the child process is already running. A turn stopped in that
|
|
318
381
|
// window must not be told a session started, nor have the agent handed the
|
|
319
382
|
// task, so the process is torn down and the abort let out instead.
|
|
320
|
-
if (
|
|
383
|
+
if (cancellation?.signal?.aborted) {
|
|
321
384
|
log.info(
|
|
322
385
|
{ acpSessionId, agentId },
|
|
323
386
|
"ACP spawn cancelled during setup; tearing the session down",
|
|
324
387
|
);
|
|
325
388
|
this.teardownSession(acpSessionId, entry);
|
|
326
|
-
|
|
389
|
+
cancellation.signal.throwIfAborted();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let modelWarning: string | undefined;
|
|
393
|
+
try {
|
|
394
|
+
({ warning: modelWarning } = await this.pinSessionModel(
|
|
395
|
+
entry,
|
|
396
|
+
configOptions,
|
|
397
|
+
requestedModel,
|
|
398
|
+
resolvedModel,
|
|
399
|
+
));
|
|
400
|
+
} catch (err) {
|
|
401
|
+
log.error(
|
|
402
|
+
{ acpSessionId, agentId, err },
|
|
403
|
+
"ACP spawn failed while applying the model",
|
|
404
|
+
);
|
|
405
|
+
const failure = this.classifyPinFailure(err, agentId, agentConfig);
|
|
406
|
+
// No prompt has fired yet, so no permissions can be pending.
|
|
407
|
+
this.teardownSession(acpSessionId, entry);
|
|
408
|
+
throw failure;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Recheck: the model pin is an await too, and everything below it either
|
|
412
|
+
// announces the session or hands the agent the task.
|
|
413
|
+
if (cancellation?.signal?.aborted) {
|
|
414
|
+
log.info(
|
|
415
|
+
{ acpSessionId, agentId },
|
|
416
|
+
"ACP spawn cancelled during the model pin; tearing the session down",
|
|
417
|
+
);
|
|
418
|
+
this.teardownSession(acpSessionId, entry);
|
|
419
|
+
cancellation.signal.throwIfAborted();
|
|
327
420
|
}
|
|
328
421
|
|
|
329
422
|
this.sendSpawnedEvent(acpSessionId, entry);
|
|
423
|
+
this.sendModelEvent(acpSessionId, entry);
|
|
330
424
|
|
|
331
425
|
// Fire prompt in the background, do not await
|
|
332
426
|
entry.currentPrompt = this.firePromptInBackground(
|
|
@@ -336,7 +430,179 @@ export class AcpSessionManager {
|
|
|
336
430
|
task,
|
|
337
431
|
);
|
|
338
432
|
|
|
339
|
-
return {
|
|
433
|
+
return {
|
|
434
|
+
acpSessionId,
|
|
435
|
+
protocolSessionId: state.acpSessionId,
|
|
436
|
+
requestedModel,
|
|
437
|
+
effectiveModel: state.model,
|
|
438
|
+
...(modelWarning ? { modelWarning } : {}),
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** Records what a config-option set says about the session's model. */
|
|
443
|
+
private applyModelInfo(
|
|
444
|
+
entry: SessionEntry,
|
|
445
|
+
configOptions: SessionConfigOption[],
|
|
446
|
+
): void {
|
|
447
|
+
this.recordModelInfo(entry, deriveModelInfo(configOptions));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Records the model state a `session/new`, `session/load`, or
|
|
452
|
+
* `session/resume` response reports. The response is authoritative when it
|
|
453
|
+
* names a selector, or when nothing has announced one yet; otherwise the
|
|
454
|
+
* selector a `config_option_update` announced while the call was open
|
|
455
|
+
* stands, because an omitted optional field is not a removal.
|
|
456
|
+
*/
|
|
457
|
+
private applyOpeningModelInfo(
|
|
458
|
+
entry: SessionEntry,
|
|
459
|
+
configOptions: SessionConfigOption[],
|
|
460
|
+
): void {
|
|
461
|
+
const info = deriveModelInfo(configOptions);
|
|
462
|
+
if (info.modelConfigId || entry.modelConfigId === undefined) {
|
|
463
|
+
this.recordModelInfo(entry, info);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Writes what the adapter reports onto the entry. `modelConfigId` doubles
|
|
469
|
+
* as the "this adapter has a model selector" flag, so state is left
|
|
470
|
+
* untouched when there is none: an adapter that reports no selector cannot
|
|
471
|
+
* wipe a model another response already named.
|
|
472
|
+
*/
|
|
473
|
+
private recordModelInfo(entry: SessionEntry, info: AcpModelInfo): void {
|
|
474
|
+
entry.modelConfigId = info.modelConfigId;
|
|
475
|
+
if (info.modelConfigId) {
|
|
476
|
+
entry.state.model = info.model;
|
|
477
|
+
entry.state.availableModels = info.availableModels;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Records what the opening response says about the model, then puts the
|
|
483
|
+
* session on `resolvedModel` if that is somewhere else. The value crosses
|
|
484
|
+
* the wire unvalidated: the adapter's own resolver accepts aliases and full
|
|
485
|
+
* ids and rejects everything else, and duplicating that judgement here
|
|
486
|
+
* would refuse values it would have taken. A refusal leaves the run on the
|
|
487
|
+
* adapter's default and comes back as the message to relay, because a
|
|
488
|
+
* session that is live and working is worth more than one that never
|
|
489
|
+
* started over a model name.
|
|
490
|
+
*
|
|
491
|
+
* An answer that carries no model selector is not a pin that landed: the
|
|
492
|
+
* session is left with no model rather than one only the superseded
|
|
493
|
+
* response ever named, and the empty picker goes out so no client is left
|
|
494
|
+
* holding a selector the answer retired.
|
|
495
|
+
*
|
|
496
|
+
* A caller who named the model is owed the news when it was not applied,
|
|
497
|
+
* whether the adapter has no selector or refused the value; an inherited
|
|
498
|
+
* rung (the per-agent config model) is only logged, so a value the caller
|
|
499
|
+
* never named does not surface as a warning on every spawn.
|
|
500
|
+
*/
|
|
501
|
+
/**
|
|
502
|
+
* A model pin that fails for authentication is the same failure a session
|
|
503
|
+
* that could not open: Claude refused the configured credential, so it is
|
|
504
|
+
* written down before teardown and surfaced in the shape the spawn boundary
|
|
505
|
+
* recognises, whether the adapter answered with a structured
|
|
506
|
+
* `auth_required` or with Claude's message-shaped 401.
|
|
507
|
+
*/
|
|
508
|
+
private classifyPinFailure(
|
|
509
|
+
err: unknown,
|
|
510
|
+
agentId: string,
|
|
511
|
+
agentConfig: { command: string; credentialDigest?: string },
|
|
512
|
+
): unknown {
|
|
513
|
+
const message = requestErrorReason(err);
|
|
514
|
+
const authCode = claudeAuthRequiredCode(err, message, {
|
|
515
|
+
command: basename(agentConfig.command),
|
|
516
|
+
});
|
|
517
|
+
if (authCode === undefined && !isAcpAuthRequired(err)) {
|
|
518
|
+
return err;
|
|
519
|
+
}
|
|
520
|
+
noteClaudeTokenRefused(agentConfig.credentialDigest, Date.now());
|
|
521
|
+
return isAcpAuthRequired(err)
|
|
522
|
+
? err
|
|
523
|
+
: new AcpAuthRequiredError(agentId, message);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private async pinSessionModel(
|
|
527
|
+
entry: SessionEntry,
|
|
528
|
+
configOptions: SessionConfigOption[],
|
|
529
|
+
requestedModel: string | undefined,
|
|
530
|
+
resolvedModel: string | undefined,
|
|
531
|
+
options?: { keepReportedModel?: boolean },
|
|
532
|
+
): Promise<ModelPinResult> {
|
|
533
|
+
const { state } = entry;
|
|
534
|
+
this.applyOpeningModelInfo(entry, configOptions);
|
|
535
|
+
|
|
536
|
+
// A resume reattaches a session the adapter restored from its own
|
|
537
|
+
// transcript, so any model it has named by now is the model the run stays
|
|
538
|
+
// on, whether the opening reply carried it or a replayed
|
|
539
|
+
// `config_option_update` did.
|
|
540
|
+
if (options?.keepReportedModel && state.model) {
|
|
541
|
+
log.info(
|
|
542
|
+
{ acpSessionId: state.id, agentId: state.agentId, model: state.model },
|
|
543
|
+
"ACP agent reported the resumed session's model; leaving it there",
|
|
544
|
+
);
|
|
545
|
+
return { applied: true };
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (!resolvedModel || resolvedModel === state.model) {
|
|
549
|
+
return { applied: true };
|
|
550
|
+
}
|
|
551
|
+
if (!entry.modelConfigId) {
|
|
552
|
+
log.info(
|
|
553
|
+
{ acpSessionId: state.id, agentId: state.agentId, resolvedModel },
|
|
554
|
+
"ACP agent advertises no model selector; running on its own model",
|
|
555
|
+
);
|
|
556
|
+
return {
|
|
557
|
+
applied: false,
|
|
558
|
+
...(requestedModel
|
|
559
|
+
? { warning: noModelSelectionWarning(state.agentId) }
|
|
560
|
+
: {}),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
try {
|
|
565
|
+
const refreshed = await entry.process.setConfigOption(
|
|
566
|
+
state.acpSessionId,
|
|
567
|
+
entry.modelConfigId,
|
|
568
|
+
resolvedModel,
|
|
569
|
+
);
|
|
570
|
+
this.applyModelInfo(entry, refreshed);
|
|
571
|
+
if (!entry.modelConfigId) {
|
|
572
|
+
// Published, not just recorded: a `config_option_update` that arrived
|
|
573
|
+
// while the round trip was open has already put its selector in front
|
|
574
|
+
// of clients, and a session no client holds yet drops the frame.
|
|
575
|
+
this.clearVanishedModelSelector(state.id, entry);
|
|
576
|
+
log.warn(
|
|
577
|
+
{ acpSessionId: state.id, agentId: state.agentId, resolvedModel },
|
|
578
|
+
"ACP agent dropped its model selector while applying the model; running on its own model",
|
|
579
|
+
);
|
|
580
|
+
return {
|
|
581
|
+
applied: false,
|
|
582
|
+
...(requestedModel
|
|
583
|
+
? { warning: noModelSelectionWarning(state.agentId) }
|
|
584
|
+
: {}),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
return { applied: true };
|
|
588
|
+
} catch (err) {
|
|
589
|
+
// Only the adapter's own answer is a refusal. A closed connection, an
|
|
590
|
+
// exited process, or a failed authentication is the caller's failure
|
|
591
|
+
// to tear down, not a model to fall back from.
|
|
592
|
+
if (!(err instanceof AcpConfigOptionRefusedError)) {
|
|
593
|
+
throw err;
|
|
594
|
+
}
|
|
595
|
+
log.warn(
|
|
596
|
+
{ acpSessionId: state.id, agentId: state.agentId, resolvedModel, err },
|
|
597
|
+
"ACP agent refused the requested model; running on its own model",
|
|
598
|
+
);
|
|
599
|
+
return {
|
|
600
|
+
applied: false,
|
|
601
|
+
...(requestedModel
|
|
602
|
+
? { warning: err instanceof Error ? err.message : String(err) }
|
|
603
|
+
: {}),
|
|
604
|
+
};
|
|
605
|
+
}
|
|
340
606
|
}
|
|
341
607
|
|
|
342
608
|
/**
|
|
@@ -362,10 +628,20 @@ export class AcpSessionManager {
|
|
|
362
628
|
// Initialize the per-session ring buffer before any update can fire.
|
|
363
629
|
this.eventBuffers.set(acpSessionId, []);
|
|
364
630
|
|
|
631
|
+
// The map is the evidence, as everywhere else in this class: an id whose
|
|
632
|
+
// entry is no longer this one was cancelled and resumed, so this
|
|
633
|
+
// process's late frames must not reach the buffer, the state, or the
|
|
634
|
+
// clients the replacement now owns. `entry` is declared below and read
|
|
635
|
+
// only from inside closures the process cannot fire until it exists.
|
|
636
|
+
const ownsSession = () => this.sessions.get(acpSessionId) === entry;
|
|
637
|
+
|
|
365
638
|
// Wrap the sender so every emitted message is mirrored into the buffer
|
|
366
639
|
// when it's an `acp_session_update`. The wrapper preserves the original
|
|
367
640
|
// call semantics: it forwards every message unchanged.
|
|
368
641
|
const wrappedSend = (msg: AssistantEvent) => {
|
|
642
|
+
if (!ownsSession()) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
369
645
|
if (msg.type === "acp_session_update") {
|
|
370
646
|
this.appendToBuffer(acpSessionId, msg);
|
|
371
647
|
} else if (msg.type === "acp_session_usage") {
|
|
@@ -386,10 +662,15 @@ export class AcpSessionManager {
|
|
|
386
662
|
opts.sendToVellum(msg);
|
|
387
663
|
};
|
|
388
664
|
|
|
665
|
+
// The callback runs once the agent is connected and sending
|
|
666
|
+
// notifications, which is after agentProcess is constructed just below.
|
|
389
667
|
const clientHandler = new VellumAcpClientHandler(
|
|
390
668
|
acpSessionId,
|
|
391
669
|
wrappedSend,
|
|
392
670
|
opts.parentConversationId,
|
|
671
|
+
(configOptions) => {
|
|
672
|
+
this.applyUnsolicitedConfigOptions(acpSessionId, entry, configOptions);
|
|
673
|
+
},
|
|
393
674
|
);
|
|
394
675
|
|
|
395
676
|
const agentProcess = new AcpAgentProcess(
|
|
@@ -444,6 +725,98 @@ export class AcpSessionManager {
|
|
|
444
725
|
});
|
|
445
726
|
}
|
|
446
727
|
|
|
728
|
+
/**
|
|
729
|
+
* Publishes the session's model selection. Silent for adapters with no
|
|
730
|
+
* model selector: the client renders nothing rather than an empty picker.
|
|
731
|
+
* A separate frame from `acp_session_spawned`, which carries a fixed subset
|
|
732
|
+
* older clients parse.
|
|
733
|
+
*/
|
|
734
|
+
private sendModelEvent(acpSessionId: string, entry: SessionEntry): void {
|
|
735
|
+
if (!entry.modelConfigId) {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
this.sendModelSnapshot(acpSessionId, entry);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/** Publishes model state with the same revision exposed by session reads. */
|
|
742
|
+
private sendModelSnapshot(acpSessionId: string, entry: SessionEntry): void {
|
|
743
|
+
const modelRevision = ++this.modelRevision;
|
|
744
|
+
entry.state.modelRevisionEpoch = this.modelRevisionEpoch;
|
|
745
|
+
entry.state.modelRevision = modelRevision;
|
|
746
|
+
entry.sendToVellum({
|
|
747
|
+
type: "acp_session_model_update",
|
|
748
|
+
acpSessionId,
|
|
749
|
+
modelRevisionEpoch: this.modelRevisionEpoch,
|
|
750
|
+
modelRevision,
|
|
751
|
+
model: entry.state.model,
|
|
752
|
+
availableModels: entry.state.availableModels ?? [],
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Whether `entry` is still this manager's live entry for `acpSessionId`.
|
|
758
|
+
* A notification that lands after close, cancellation, or prompt completion
|
|
759
|
+
* has nothing left to update: the terminal row is already written, so
|
|
760
|
+
* applying it would leave history on one model and clients on another.
|
|
761
|
+
*/
|
|
762
|
+
private isEntryLive(acpSessionId: string, entry: SessionEntry): boolean {
|
|
763
|
+
return (
|
|
764
|
+
this.sessions.get(acpSessionId) === entry &&
|
|
765
|
+
isLiveAcpStatus(entry.state.status)
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Drops the live model snapshot a vanished selector took with it and
|
|
771
|
+
* publishes the empty picker. `applyModelInfo` keeps the model already
|
|
772
|
+
* recorded for adapters that never had a selector, which here would leave
|
|
773
|
+
* the client holding options the session cannot reach. Returns whether it
|
|
774
|
+
* fired, so callers skip the publish that assumes a model is still there.
|
|
775
|
+
*
|
|
776
|
+
* A published picker is what says the selector was there to vanish: only an
|
|
777
|
+
* adapter that advertised one ever populates it, so an adapter that never
|
|
778
|
+
* had one publishes nothing.
|
|
779
|
+
*/
|
|
780
|
+
private clearVanishedModelSelector(
|
|
781
|
+
acpSessionId: string,
|
|
782
|
+
entry: SessionEntry,
|
|
783
|
+
): boolean {
|
|
784
|
+
if (entry.modelConfigId || entry.state.availableModels === undefined) {
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
entry.state.model = undefined;
|
|
788
|
+
entry.state.availableModels = [];
|
|
789
|
+
this.sendModelSnapshot(acpSessionId, entry);
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Takes in a `config_option_update` the adapter sent unprompted, which is
|
|
795
|
+
* how a `/model` the user typed straight into the transcript reaches the
|
|
796
|
+
* daemon: record what it reports, publish it, and drop the live snapshot
|
|
797
|
+
* when the selector it announced has vanished.
|
|
798
|
+
*
|
|
799
|
+
* A session past its terminal transition takes nothing from a late
|
|
800
|
+
* notification at all: its history row is already written, so mutating
|
|
801
|
+
* state or emitting here would leave clients and history disagreeing about
|
|
802
|
+
* the same run.
|
|
803
|
+
*/
|
|
804
|
+
private applyUnsolicitedConfigOptions(
|
|
805
|
+
acpSessionId: string,
|
|
806
|
+
entry: SessionEntry,
|
|
807
|
+
configOptions: SessionConfigOption[],
|
|
808
|
+
): void {
|
|
809
|
+
if (!this.isEntryLive(acpSessionId, entry)) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
this.applyModelInfo(entry, configOptions);
|
|
813
|
+
|
|
814
|
+
if (this.clearVanishedModelSelector(acpSessionId, entry)) {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
this.sendModelEvent(acpSessionId, entry);
|
|
818
|
+
}
|
|
819
|
+
|
|
447
820
|
/**
|
|
448
821
|
* Resumes a terminal-state session from its persisted
|
|
449
822
|
* `acp_session_history` row, reattaching to the agent's stored
|
|
@@ -623,6 +996,7 @@ export class AcpSessionManager {
|
|
|
623
996
|
// Seed before the child process spawns so no live update can fire first.
|
|
624
997
|
entry.clientHandler.seedSeq(maxSeq);
|
|
625
998
|
|
|
999
|
+
let configOptions: SessionConfigOption[] = [];
|
|
626
1000
|
try {
|
|
627
1001
|
log.info(
|
|
628
1002
|
{ acpSessionId, agentId: row.agentId },
|
|
@@ -632,14 +1006,20 @@ export class AcpSessionManager {
|
|
|
632
1006
|
await agentProcess.initialize();
|
|
633
1007
|
if (agentProcess.supportsSessionResume) {
|
|
634
1008
|
// session/resume reattaches without replaying history.
|
|
635
|
-
await agentProcess.resumeSession(
|
|
1009
|
+
({ configOptions } = await agentProcess.resumeSession(
|
|
1010
|
+
row.acpSessionId,
|
|
1011
|
+
row.cwd,
|
|
1012
|
+
));
|
|
636
1013
|
} else if (agentProcess.supportsLoadSession) {
|
|
637
1014
|
// session/load replays the full history as session/update
|
|
638
1015
|
// notifications before resolving; suppress forwarding so the
|
|
639
1016
|
// conversation and ring buffer don't receive duplicates.
|
|
640
1017
|
entry.clientHandler.beginReplaySuppression();
|
|
641
1018
|
try {
|
|
642
|
-
await agentProcess.loadSession(
|
|
1019
|
+
({ configOptions } = await agentProcess.loadSession(
|
|
1020
|
+
row.acpSessionId,
|
|
1021
|
+
row.cwd,
|
|
1022
|
+
));
|
|
643
1023
|
} finally {
|
|
644
1024
|
entry.clientHandler.endReplaySuppression();
|
|
645
1025
|
}
|
|
@@ -668,7 +1048,43 @@ export class AcpSessionManager {
|
|
|
668
1048
|
throw err;
|
|
669
1049
|
}
|
|
670
1050
|
|
|
1051
|
+
// The ladder a spawn walks, for the case the adapter reports no model of
|
|
1052
|
+
// its own; a reattached session that comes back naming one keeps it.
|
|
1053
|
+
const resolvedModel = resolveAcpModel({ agentModel: agentConfig.model });
|
|
1054
|
+
let applied: boolean;
|
|
1055
|
+
try {
|
|
1056
|
+
({ applied } = await this.pinSessionModel(
|
|
1057
|
+
entry,
|
|
1058
|
+
configOptions,
|
|
1059
|
+
undefined,
|
|
1060
|
+
resolvedModel,
|
|
1061
|
+
{ keepReportedModel: true },
|
|
1062
|
+
));
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
log.error(
|
|
1065
|
+
{ acpSessionId, agentId: row.agentId, err },
|
|
1066
|
+
"ACP resume failed while applying the model",
|
|
1067
|
+
);
|
|
1068
|
+
const failure = this.classifyPinFailure(err, row.agentId, agentConfig);
|
|
1069
|
+
this.teardownSession(acpSessionId, entry);
|
|
1070
|
+
throw failure;
|
|
1071
|
+
}
|
|
1072
|
+
if (resolvedModel && !applied) {
|
|
1073
|
+
// State keeps the adapter's own answer, so the model event, the status
|
|
1074
|
+
// projection and the panel all name the model the run is really on.
|
|
1075
|
+
log.warn(
|
|
1076
|
+
{
|
|
1077
|
+
acpSessionId,
|
|
1078
|
+
agentId: row.agentId,
|
|
1079
|
+
resolvedModel,
|
|
1080
|
+
model: state.model,
|
|
1081
|
+
},
|
|
1082
|
+
"ACP resume could not apply the configured model; running on the agent's own model",
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
671
1086
|
this.sendSpawnedEvent(acpSessionId, entry);
|
|
1087
|
+
this.sendModelEvent(acpSessionId, entry);
|
|
672
1088
|
}
|
|
673
1089
|
|
|
674
1090
|
/**
|
|
@@ -929,10 +1345,7 @@ export class AcpSessionManager {
|
|
|
929
1345
|
if (!entry) {
|
|
930
1346
|
throw new AcpSessionNotFoundError(acpSessionId);
|
|
931
1347
|
}
|
|
932
|
-
if (
|
|
933
|
-
entry.state.status === "running" ||
|
|
934
|
-
entry.state.status === "initializing"
|
|
935
|
-
) {
|
|
1348
|
+
if (isLiveAcpStatus(entry.state.status)) {
|
|
936
1349
|
entry.state.status = "cancelled";
|
|
937
1350
|
entry.state.completedAt = Date.now();
|
|
938
1351
|
}
|
|
@@ -942,6 +1355,13 @@ export class AcpSessionManager {
|
|
|
942
1355
|
|
|
943
1356
|
/**
|
|
944
1357
|
* Denies pending ACP permissions, kills the process, and removes the session.
|
|
1358
|
+
*
|
|
1359
|
+
* The permissions and the process belong to `entry`, so they are always
|
|
1360
|
+
* torn down; the map slot and its buffer belong to whichever entry holds
|
|
1361
|
+
* the id, so they are only cleared while that is still this one. A caller
|
|
1362
|
+
* whose entry the map has since replaced (a cancel that persisted a
|
|
1363
|
+
* resumable row, then a resume of the same id, while an await was pending)
|
|
1364
|
+
* would otherwise evict the live session that took its place.
|
|
945
1365
|
*/
|
|
946
1366
|
private teardownSession(acpSessionId: string, entry: SessionEntry): void {
|
|
947
1367
|
for (const requestId of entry.clientHandler.pendingRequestIds) {
|
|
@@ -951,6 +1371,9 @@ export class AcpSessionManager {
|
|
|
951
1371
|
}
|
|
952
1372
|
}
|
|
953
1373
|
entry.process.kill();
|
|
1374
|
+
if (this.sessions.get(acpSessionId) !== entry) {
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
954
1377
|
this.sessions.delete(acpSessionId);
|
|
955
1378
|
// Free the buffer in case persistTerminal hasn't already (e.g. close()
|
|
956
1379
|
// before terminal transition).
|