@vellumai/assistant 0.8.7 → 0.8.8-dev.202606052332.17fc8ea
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/Dockerfile +20 -4
- package/bun.lock +2 -2
- package/docker-entrypoint.sh +4 -2
- package/docker-init-apt-root.sh +3 -1
- package/docker-kata-apt-env.sh +3 -1
- package/docker-kata-runtime-family.sh +12 -0
- package/docs/architecture/memory.md +1 -1
- package/examples/plugins/echo/README.md +61 -66
- package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
- package/examples/plugins/echo/hooks/stop.ts +16 -0
- package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
- package/examples/plugins/echo/package.json +1 -2
- package/examples/plugins/echo/src/emit.ts +19 -0
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
- package/openapi.yaml +3378 -335
- package/package.json +2 -2
- package/scripts/generate-openapi.ts +68 -41
- package/src/__tests__/agent-loop-exit-reason.test.ts +35 -93
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +37 -87
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -3
- package/src/__tests__/anthropic-provider.test.ts +95 -2
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +4 -1
- package/src/__tests__/assistant-event-hub.test.ts +25 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
- package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
- package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
- package/src/__tests__/btw-routes.test.ts +62 -3
- package/src/__tests__/build-persisted-content.test.ts +184 -0
- package/src/__tests__/catalog-files.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +1 -1
- package/src/__tests__/channel-approvals.test.ts +1 -1
- package/src/__tests__/clawhub-files.test.ts +1 -1
- package/src/__tests__/compaction-circuit.test.ts +258 -0
- package/src/__tests__/compaction-direct.test.ts +132 -0
- package/src/__tests__/compaction.benchmark.test.ts +0 -30
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -5
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -7
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +316 -1143
- package/src/__tests__/conversation-agent-loop.test.ts +638 -1655
- package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-history-web-search.test.ts +11 -1
- package/src/__tests__/conversation-pairing.test.ts +4 -31
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -10
- package/src/__tests__/conversation-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
- package/src/__tests__/conversation-runtime-assembly.test.ts +310 -300
- package/src/__tests__/conversation-runtime-workspace.test.ts +105 -45
- package/src/__tests__/conversation-slash-commands.test.ts +8 -42
- package/src/__tests__/conversation-slash-queue.test.ts +6 -1
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
- package/src/__tests__/conversation-sync-tags.test.ts +27 -15
- package/src/__tests__/conversation-title-service.test.ts +135 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +17 -16
- package/src/__tests__/conversation-workspace-injection.test.ts +67 -2
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +7 -6
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/cross-provider-web-search.test.ts +214 -1
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
- package/src/__tests__/dm-persistence.test.ts +5 -1
- package/src/__tests__/dynamic-page-surface.test.ts +31 -0
- package/src/__tests__/empty-response-hook.test.ts +304 -0
- package/src/__tests__/feature-flag-test-helpers.ts +2 -2
- package/src/__tests__/file-write-tool.test.ts +63 -0
- package/src/__tests__/gateway-only-guard.test.ts +12 -2
- package/src/__tests__/gemini-image-service.test.ts +13 -0
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/helpers/mock-provider.ts +110 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
- package/src/__tests__/history-repair-hook.test.ts +1 -0
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/identity-intro-cache.test.ts +12 -100
- package/src/__tests__/identity-routes.test.ts +248 -7
- package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
- package/src/__tests__/injector-background-turn.test.ts +3 -9
- package/src/__tests__/injector-chain.test.ts +139 -275
- package/src/__tests__/injector-disk-pressure.test.ts +75 -41
- package/src/__tests__/injector-document-comments.test.ts +3 -3
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
- package/src/__tests__/injector-v3-suppression.test.ts +31 -37
- package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
- package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
- package/src/__tests__/list-messages-page-latest.test.ts +60 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
- package/src/__tests__/llm-usage-store.test.ts +223 -1
- package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
- package/src/__tests__/native-web-search.test.ts +191 -0
- package/src/__tests__/onboarding-template-contract.test.ts +2 -0
- package/src/__tests__/openai-image-service.test.ts +17 -0
- package/src/__tests__/openai-provider.test.ts +31 -1
- package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -284
- package/src/__tests__/persist-unsendable-image.test.ts +215 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
- package/src/__tests__/pkb-autoinject.test.ts +2 -5
- package/src/__tests__/plugin-api-shim.test.ts +3 -6
- package/src/__tests__/plugin-bootstrap.test.ts +14 -40
- package/src/__tests__/plugin-registry.test.ts +3 -76
- package/src/__tests__/plugin-types.test.ts +0 -193
- package/src/__tests__/process-message-display-content.test.ts +6 -2
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
- package/src/__tests__/resolve-trust-class.test.ts +4 -4
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
- package/src/__tests__/schedule-routes.test.ts +603 -2
- package/src/__tests__/schedule-store.test.ts +41 -0
- package/src/__tests__/schedule-tools.test.ts +35 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/server-history-render.test.ts +314 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
- package/src/__tests__/skillssh-files.test.ts +1 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
- package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
- package/src/__tests__/subagent-manager-notify.test.ts +1 -3
- package/src/__tests__/subagent-notify-parent.test.ts +1 -3
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
- package/src/__tests__/system-prompt.test.ts +20 -0
- package/src/__tests__/task-scheduler.test.ts +162 -1
- package/src/__tests__/terminal-tools.test.ts +6 -1
- package/src/__tests__/title-generate-hook.test.ts +319 -0
- package/src/__tests__/tool-error-hook.test.ts +278 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -2
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
- package/src/__tests__/ui-work-result-surface.test.ts +159 -0
- package/src/__tests__/usage-routes.test.ts +285 -1
- package/src/__tests__/user-plugin-loader.test.ts +54 -286
- package/src/__tests__/voice-session-bridge.test.ts +6 -3
- package/src/__tests__/web-search-backend-failure.test.ts +166 -0
- package/src/acp/__tests__/agent-process.test.ts +161 -0
- package/src/acp/__tests__/client-handler.test.ts +40 -0
- package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
- package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +196 -0
- package/src/acp/auto-install.ts +177 -0
- package/src/acp/client-handler.ts +31 -0
- package/src/acp/feature-gate.test.ts +48 -0
- package/src/acp/feature-gate.ts +34 -0
- package/src/acp/prepare-agent-env.ts +83 -29
- package/src/acp/resolve-agent.test.ts +320 -7
- package/src/acp/resolve-agent.ts +182 -18
- package/src/acp/resume-hint.ts +25 -0
- package/src/acp/session-manager.ts +495 -73
- package/src/acp/types.ts +8 -0
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +362 -485
- package/src/api/events/assistant-thinking-delta.ts +33 -0
- package/src/api/events/tool-output-chunk.ts +45 -0
- package/src/api/events/tool-use-preview-start.ts +32 -0
- package/src/api/events/trace-event.ts +69 -0
- package/src/api/index.ts +48 -13
- package/src/api/responses/conversation-message.ts +374 -0
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/avatar/__tests__/avatar-store.test.ts +34 -29
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/__tests__/notifications.test.ts +58 -14
- package/src/cli/commands/notifications.ts +112 -60
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +10 -0
- package/src/config/acp-defaults.ts +6 -0
- package/src/config/assistant-feature-flags.ts +22 -11
- package/src/config/bundled-skills/acp/SKILL.md +83 -31
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +224 -398
- package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
- package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
- package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
- package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
- package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +0 -7
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/feature-flag-cache.ts +3 -3
- package/src/config/feature-flag-registry.json +48 -7
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-v2.ts +8 -0
- package/src/config/schemas/memory-v3.ts +8 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills.ts +13 -0
- package/src/context/compactor.ts +1 -1
- package/src/context/strip-injections.ts +128 -0
- package/src/context/token-estimator.ts +23 -0
- package/src/context/tool-result-truncation.ts +0 -23
- package/src/context/window-manager.ts +5 -7
- package/src/credential-execution/executable-discovery.ts +16 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
- package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/config-watcher.ts +2 -2
- package/src/daemon/context-overflow-reducer.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +594 -153
- package/src/daemon/conversation-agent-loop.ts +301 -997
- package/src/daemon/conversation-history.ts +5 -4
- package/src/daemon/conversation-lifecycle.ts +3 -4
- package/src/daemon/conversation-messaging.ts +7 -6
- package/src/daemon/conversation-process.ts +11 -16
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +218 -398
- package/src/daemon/conversation-slash.ts +6 -25
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +222 -4
- package/src/daemon/conversation-tool-setup.ts +2 -29
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +32 -20
- package/src/daemon/external-plugins-bootstrap.ts +17 -18
- package/src/daemon/handlers/config-a2a.ts +51 -36
- package/src/daemon/handlers/config-slack-channel.ts +20 -14
- package/src/daemon/handlers/config-telegram.ts +16 -2
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/shared.ts +156 -84
- package/src/daemon/handlers/skills.ts +42 -10
- package/src/daemon/lifecycle.ts +25 -0
- package/src/daemon/message-types/apps.ts +1 -29
- package/src/daemon/message-types/messages.ts +9 -57
- package/src/daemon/message-types/skills.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +136 -3
- package/src/daemon/now-scratchpad.ts +21 -0
- package/src/daemon/orphan-reaper.test.ts +210 -0
- package/src/daemon/orphan-reaper.ts +240 -0
- package/src/daemon/overflow-reduction-loop.ts +230 -0
- package/src/daemon/persist-unsendable-image.ts +117 -0
- package/src/daemon/process-message.ts +1 -3
- package/src/daemon/server.ts +2 -0
- package/src/daemon/trace-emitter.ts +6 -4
- package/src/daemon/trust-context.ts +19 -0
- package/src/daemon/wake-target-adapter.ts +3 -1
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
- package/src/heartbeat/heartbeat-run-store.ts +23 -1
- package/src/heartbeat/heartbeat-service.ts +26 -0
- package/src/home/home-greeting-cache.ts +24 -1
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/gateway-client.test.ts +2 -2
- package/src/ipc/gateway-client.ts +3 -3
- package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
- package/src/ipc/skill-routes/memory.ts +4 -2
- package/src/media/gemini-image-service.ts +15 -0
- package/src/media/openai-image-service.ts +14 -0
- package/src/media/types.ts +34 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
- package/src/memory/auth-fallback-events-store.ts +94 -0
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/conversation-title-service.ts +65 -41
- package/src/memory/db-init.ts +6 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
- package/src/memory/graph/conversation-graph-memory.ts +65 -0
- package/src/memory/job-handlers/conversation-starters.ts +13 -2
- package/src/memory/jobs-store.ts +33 -0
- package/src/memory/jobs-worker.ts +32 -5
- package/src/memory/llm-usage-store.ts +224 -50
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
- package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
- package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
- package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/pkb/autoinject.ts +61 -0
- package/src/memory/pkb/context.ts +50 -0
- package/src/memory/pkb/types.ts +14 -0
- package/src/memory/schedule-attribution-sql.ts +104 -0
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/schema/infrastructure.ts +16 -0
- package/src/memory/usage-grouped-buckets.ts +6 -1
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -4
- package/src/memory/v2/consolidation-job.ts +14 -5
- package/src/notifications/conversation-pairing.ts +8 -15
- package/src/notifications/decision-engine.ts +6 -3
- package/src/notifications/home-feed-side-effect.ts +12 -1
- package/src/permissions/prompter.ts +4 -0
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +7 -5
- package/src/plugin-api/types.ts +151 -1
- package/src/plugins/defaults/compaction/compact.ts +59 -0
- package/src/plugins/defaults/compaction/package.json +1 -1
- package/src/plugins/defaults/compaction/register.ts +8 -19
- package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
- package/src/plugins/defaults/empty-response/register.ts +8 -13
- package/src/plugins/defaults/index.ts +2 -18
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
- package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +288 -81
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +16 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +48 -12
- package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +104 -32
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +96 -30
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +34 -16
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
- package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +8 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +26 -14
- package/src/plugins/defaults/{llm-call → memory-v3-shadow}/package.json +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +3 -3
- package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +51 -45
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +61 -46
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +69 -99
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +8 -0
- package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
- package/src/plugins/defaults/title-generate/package.json +1 -1
- package/src/plugins/defaults/title-generate/register.ts +18 -18
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
- package/src/plugins/defaults/tool-error/package.json +1 -1
- package/src/plugins/defaults/tool-error/register.ts +9 -21
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
- package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
- package/src/plugins/external-api.ts +2 -2
- package/src/plugins/pipeline.ts +6 -305
- package/src/plugins/registry.ts +10 -55
- package/src/plugins/types.ts +62 -797
- package/src/plugins/user-loader.ts +30 -127
- package/src/proactive-artifact/aux-message-injector.ts +4 -4
- package/src/proactive-artifact/job.test.ts +8 -13
- package/src/prompts/__tests__/system-prompt.test.ts +42 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +15 -0
- package/src/providers/anthropic/client.ts +37 -29
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -0
- package/src/providers/openrouter/client.ts +1 -0
- package/src/providers/placeholder-sentinels.ts +35 -0
- package/src/runtime/__tests__/agent-wake.test.ts +10 -6
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +2 -5
- package/src/runtime/assistant-event-hub.ts +37 -7
- package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/http-router.ts +16 -21
- package/src/runtime/http-types.ts +16 -70
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
- package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
- package/src/runtime/routes/acp-routes.test.ts +89 -25
- package/src/runtime/routes/acp-routes.ts +81 -29
- package/src/runtime/routes/app-management-routes.ts +6 -117
- package/src/runtime/routes/app-routes.ts +13 -15
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/attachment-routes.ts +26 -15
- package/src/runtime/routes/avatar-routes.ts +26 -0
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +6 -10
- package/src/runtime/routes/btw-routes.ts +29 -23
- package/src/runtime/routes/consolidation-routes.ts +120 -20
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +3 -1
- package/src/runtime/routes/conversation-routes.ts +372 -185
- package/src/runtime/routes/conversation-starter-routes.ts +13 -7
- package/src/runtime/routes/conversations-import-routes.ts +24 -7
- package/src/runtime/routes/documents-routes.ts +4 -0
- package/src/runtime/routes/domain-routes.ts +51 -37
- package/src/runtime/routes/epoch-millis-range.ts +34 -0
- package/src/runtime/routes/events-routes.ts +28 -34
- package/src/runtime/routes/gateway-log-routes.ts +26 -4
- package/src/runtime/routes/heartbeat-routes.ts +32 -12
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-cu-routes.ts +1 -1
- package/src/runtime/routes/identity-intro-cache.ts +11 -34
- package/src/runtime/routes/identity-routes.ts +224 -18
- package/src/runtime/routes/image-generation-routes.ts +40 -2
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/a2a.ts +12 -10
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
- package/src/runtime/routes/integrations/slack/channel.ts +4 -0
- package/src/runtime/routes/integrations/slack/share.ts +27 -6
- package/src/runtime/routes/integrations/telegram.ts +6 -0
- package/src/runtime/routes/integrations/twilio.ts +42 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
- package/src/runtime/routes/log-export-routes.ts +8 -0
- package/src/runtime/routes/memory-v2-routes.ts +15 -8
- package/src/runtime/routes/memory-v3-routes.ts +66 -34
- package/src/runtime/routes/oauth-apps.ts +66 -12
- package/src/runtime/routes/oauth-providers.ts +44 -5
- package/src/runtime/routes/platform-routes.ts +81 -5
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
- package/src/runtime/routes/playground/force-compact.ts +1 -1
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/rename-conversation-routes.ts +5 -0
- package/src/runtime/routes/schedule-routes.ts +152 -42
- package/src/runtime/routes/secret-routes.ts +14 -2
- package/src/runtime/routes/skills-routes.ts +43 -14
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
- package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
- package/src/runtime/routes/trust-rules-routes.ts +26 -2
- package/src/runtime/routes/tts-routes.ts +35 -0
- package/src/runtime/routes/types.ts +66 -8
- package/src/runtime/routes/usage-routes.ts +47 -39
- package/src/runtime/routes/webhook-routes.ts +41 -2
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/routes/workspace-routes.ts +4 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
- package/src/runtime/services/analyze-conversation.ts +2 -2
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/schedule/schedule-store.ts +20 -1
- package/src/schedule/schedule-usage-store.ts +83 -0
- package/src/schedule/scheduler.ts +12 -5
- package/src/signals/cancel.ts +2 -4
- package/src/skills/catalog-files.ts +2 -2
- package/src/skills/catalog-install.ts +3 -0
- package/src/skills/categories-cache.ts +118 -0
- package/src/skills/clawhub-files.ts +1 -2
- package/src/skills/skillssh-files.ts +1 -2
- package/src/subagent/manager.ts +17 -5
- package/src/telemetry/types.ts +29 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
- package/src/telemetry/usage-telemetry-reporter.ts +57 -2
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +7 -1
- package/src/tools/acp/spawn.test.ts +158 -55
- package/src/tools/acp/spawn.ts +47 -72
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +13 -8
- package/src/tools/executor.ts +1 -53
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
- package/src/tools/network/__tests__/web-search.test.ts +11 -3
- package/src/tools/network/web-search-error.test.ts +248 -0
- package/src/tools/network/web-search-error.ts +267 -0
- package/src/tools/network/web-search.ts +207 -48
- package/src/tools/schedule/create.ts +2 -0
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/terminal/safe-env.ts +10 -1
- package/src/tools/ui-surface/definitions.ts +34 -5
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
- package/src/tts/provider-catalog.ts +76 -1
- package/src/util/mutex.ts +47 -0
- package/src/workspace/git-service.ts +1 -42
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
- package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
- package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/docs/plugins.md +0 -836
- package/examples/plugins/echo/register.ts +0 -184
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
- package/src/__tests__/compaction-pipeline.test.ts +0 -210
- package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
- package/src/__tests__/empty-response-pipeline.test.ts +0 -423
- package/src/__tests__/llm-call-pipeline.test.ts +0 -287
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
- package/src/__tests__/persistence-pipeline.test.ts +0 -503
- package/src/__tests__/pipeline-runner.test.ts +0 -564
- package/src/__tests__/title-generate-pipeline.test.ts +0 -211
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
- package/src/__tests__/tool-error-pipeline.test.ts +0 -241
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
- package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
- package/src/gallery/default-gallery.ts +0 -1359
- package/src/gallery/gallery-manifest.ts +0 -28
- package/src/home/feature-gate.ts +0 -22
- package/src/memory/v3/provider-blocks.ts +0 -16
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
- package/src/plugins/defaults/circuit-breaker/package.json +0 -15
- package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
- package/src/plugins/defaults/compaction/terminal.ts +0 -73
- package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
- package/src/plugins/defaults/empty-response/terminal.ts +0 -106
- package/src/plugins/defaults/injectors/package.json +0 -15
- package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
- package/src/plugins/defaults/llm-call/register.ts +0 -45
- package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
- package/src/plugins/defaults/memory-retrieval/package.json +0 -15
- package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
- package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
- package/src/plugins/defaults/overflow-reduce/package.json +0 -15
- package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
- package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
- package/src/plugins/defaults/persistence/package.json +0 -15
- package/src/plugins/defaults/persistence/register.ts +0 -38
- package/src/plugins/defaults/persistence/terminal.ts +0 -83
- package/src/plugins/defaults/title-generate/terminal.ts +0 -31
- package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
- package/src/plugins/defaults/token-estimate/package.json +0 -15
- package/src/plugins/defaults/token-estimate/register.ts +0 -34
- package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
- package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
- package/src/plugins/defaults/tool-error/terminal.ts +0 -47
- package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
- package/src/plugins/defaults/tool-execute/package.json +0 -15
- package/src/plugins/defaults/tool-execute/register.ts +0 -49
- package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
- package/src/skills/category-inference.ts +0 -111
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
package/src/acp/agent-process.ts
CHANGED
|
@@ -34,6 +34,7 @@ export type AcpClientFactory = (agent: Agent) => Client;
|
|
|
34
34
|
export class AcpAgentProcess {
|
|
35
35
|
private proc: ChildProcess | null = null;
|
|
36
36
|
private connection: acp.ClientSideConnection | null = null;
|
|
37
|
+
private initializeResponse: InitializeResponse | null = null;
|
|
37
38
|
|
|
38
39
|
constructor(
|
|
39
40
|
public readonly agentId: string,
|
|
@@ -99,7 +100,7 @@ export class AcpAgentProcess {
|
|
|
99
100
|
|
|
100
101
|
log.info({ agentId: this.agentId }, "Initializing ACP connection");
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
const response = await this.connection.initialize({
|
|
103
104
|
protocolVersion: acp.PROTOCOL_VERSION,
|
|
104
105
|
clientInfo: { name: "vellum", version: "1.0.0" },
|
|
105
106
|
clientCapabilities: {
|
|
@@ -107,6 +108,28 @@ export class AcpAgentProcess {
|
|
|
107
108
|
terminal: true,
|
|
108
109
|
},
|
|
109
110
|
});
|
|
111
|
+
|
|
112
|
+
this.initializeResponse = response;
|
|
113
|
+
return response;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Whether the agent advertised support for `session/load` at initialize.
|
|
118
|
+
* Returns false before initialize() resolves.
|
|
119
|
+
*/
|
|
120
|
+
get supportsLoadSession(): boolean {
|
|
121
|
+
return this.initializeResponse?.agentCapabilities?.loadSession === true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Whether the agent advertised support for `session/resume` at initialize.
|
|
126
|
+
* Returns false before initialize() resolves.
|
|
127
|
+
*/
|
|
128
|
+
get supportsSessionResume(): boolean {
|
|
129
|
+
return (
|
|
130
|
+
this.initializeResponse?.agentCapabilities?.sessionCapabilities?.resume !=
|
|
131
|
+
null
|
|
132
|
+
);
|
|
110
133
|
}
|
|
111
134
|
|
|
112
135
|
/**
|
|
@@ -128,6 +151,41 @@ export class AcpAgentProcess {
|
|
|
128
151
|
return result.sessionId;
|
|
129
152
|
}
|
|
130
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Loads a previously persisted ACP session via `session/load`.
|
|
156
|
+
*
|
|
157
|
+
* Per the ACP spec, the agent replays the session's conversation history
|
|
158
|
+
* as `session/update` notifications before the load response resolves;
|
|
159
|
+
* callers should suppress forwarding of those replayed updates (see
|
|
160
|
+
* VellumAcpClientHandler.beginReplaySuppression).
|
|
161
|
+
*/
|
|
162
|
+
async loadSession(sessionId: string, cwd: string): Promise<void> {
|
|
163
|
+
if (!this.connection) {
|
|
164
|
+
throw new Error(`ACP agent "${this.agentId}" is not spawned`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
log.info({ agentId: this.agentId, sessionId, cwd }, "Loading ACP session");
|
|
168
|
+
|
|
169
|
+
await this.connection.loadSession({ sessionId, cwd, mcpServers: [] });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Resumes a previously persisted ACP session via `session/resume`.
|
|
174
|
+
*
|
|
175
|
+
* Unlike `session/load`, resume performs no history replay, so it is
|
|
176
|
+
* preferred when the agent advertises the capability
|
|
177
|
+
* (see supportsSessionResume).
|
|
178
|
+
*/
|
|
179
|
+
async resumeSession(sessionId: string, cwd: string): Promise<void> {
|
|
180
|
+
if (!this.connection) {
|
|
181
|
+
throw new Error(`ACP agent "${this.agentId}" is not spawned`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
log.info({ agentId: this.agentId, sessionId, cwd }, "Resuming ACP session");
|
|
185
|
+
|
|
186
|
+
await this.connection.resumeSession({ sessionId, cwd, mcpServers: [] });
|
|
187
|
+
}
|
|
188
|
+
|
|
131
189
|
/**
|
|
132
190
|
* Sends a prompt to an existing ACP session.
|
|
133
191
|
* Returns the prompt response (includes stopReason).
|
|
@@ -175,6 +233,7 @@ export class AcpAgentProcess {
|
|
|
175
233
|
this.proc = null;
|
|
176
234
|
}
|
|
177
235
|
this.connection = null;
|
|
236
|
+
this.initializeResponse = null;
|
|
178
237
|
}
|
|
179
238
|
|
|
180
239
|
/**
|
|
@@ -205,5 +264,6 @@ export class AcpAgentProcess {
|
|
|
205
264
|
|
|
206
265
|
this.proc = null;
|
|
207
266
|
this.connection = null;
|
|
267
|
+
this.initializeResponse = null;
|
|
208
268
|
}
|
|
209
269
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the silent ACP adapter auto-installer.
|
|
3
|
+
*
|
|
4
|
+
* `execFile` is stubbed via the shared `installExecFileStub` helper: a
|
|
5
|
+
* process-global `mock.module("node:child_process", ...)` driven by per-call
|
|
6
|
+
* scripted responses keyed on `${command} ${args[0]}`. The
|
|
7
|
+
* `resolveAgentWithAutoInstall` ordering suite additionally stubs the ACP
|
|
8
|
+
* config and `Bun.which` so it can flip bun / adapter-binary presence.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import { installAcpConfigStub } from "./__tests__/helpers/acp-config-stub.js";
|
|
14
|
+
import { installExecFileStub } from "./__tests__/helpers/exec-file-stub.js";
|
|
15
|
+
import { installWhichStub } from "./__tests__/helpers/which-stub.js";
|
|
16
|
+
|
|
17
|
+
const { execScripts, execFileMock, reset } = installExecFileStub();
|
|
18
|
+
const config = await installAcpConfigStub();
|
|
19
|
+
const which = installWhichStub();
|
|
20
|
+
|
|
21
|
+
afterAll(() => {
|
|
22
|
+
which.restore();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Spread the real module so other test files that load logger consumers
|
|
26
|
+
// (e.g. `truncateForLog` importers) after this process-global mock still
|
|
27
|
+
// resolve every named export.
|
|
28
|
+
const realLogger = await import("../util/logger.js");
|
|
29
|
+
mock.module("../util/logger.js", () => ({
|
|
30
|
+
...realLogger,
|
|
31
|
+
getLogger: () =>
|
|
32
|
+
new Proxy({} as Record<string, unknown>, {
|
|
33
|
+
get: () => () => {},
|
|
34
|
+
}),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
ensureAdapterInstalled,
|
|
39
|
+
resolveAgentWithAutoInstall,
|
|
40
|
+
_resetAdapterInstallCacheForTests,
|
|
41
|
+
} = await import("./auto-install.js");
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
reset();
|
|
45
|
+
_resetAdapterInstallCacheForTests();
|
|
46
|
+
config.setConfig({ agents: {} });
|
|
47
|
+
which.setWhich({});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("ensureAdapterInstalled", () => {
|
|
51
|
+
test("known command: runs `npm i -g <pkg>` and reports installed", async () => {
|
|
52
|
+
execScripts.set("npm i", { stdout: "" });
|
|
53
|
+
|
|
54
|
+
const result = await ensureAdapterInstalled("claude-agent-acp");
|
|
55
|
+
|
|
56
|
+
expect(result).toEqual({ installed: true });
|
|
57
|
+
expect(execFileMock).toHaveBeenCalledTimes(1);
|
|
58
|
+
const [command, args] = execFileMock.mock.calls[0];
|
|
59
|
+
expect(command).toBe("npm");
|
|
60
|
+
expect(args).toEqual([
|
|
61
|
+
"i",
|
|
62
|
+
"-g",
|
|
63
|
+
"@agentclientprotocol/claude-agent-acp",
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("unknown command: never invokes npm (security allowlist)", async () => {
|
|
68
|
+
const result = await ensureAdapterInstalled("some-arbitrary-binary");
|
|
69
|
+
|
|
70
|
+
expect(result.installed).toBe(false);
|
|
71
|
+
expect(result.error).toBeUndefined();
|
|
72
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("npm failure: reports the error and does not install", async () => {
|
|
76
|
+
execScripts.set("npm i", {
|
|
77
|
+
error: new Error("EACCES: permission denied"),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const result = await ensureAdapterInstalled("codex-acp");
|
|
81
|
+
|
|
82
|
+
expect(result.installed).toBe(false);
|
|
83
|
+
expect(result.error).toContain("EACCES");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("failed install is retried on the next call", async () => {
|
|
87
|
+
execScripts.set("npm i", { error: new Error("network down") });
|
|
88
|
+
const first = await ensureAdapterInstalled("claude-agent-acp");
|
|
89
|
+
expect(first.installed).toBe(false);
|
|
90
|
+
|
|
91
|
+
execScripts.set("npm i", { stdout: "" });
|
|
92
|
+
const second = await ensureAdapterInstalled("claude-agent-acp");
|
|
93
|
+
expect(second.installed).toBe(true);
|
|
94
|
+
expect(execFileMock).toHaveBeenCalledTimes(2);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("successful install is cached for the process lifetime", async () => {
|
|
98
|
+
execScripts.set("npm i", { stdout: "" });
|
|
99
|
+
|
|
100
|
+
await ensureAdapterInstalled("claude-agent-acp");
|
|
101
|
+
await ensureAdapterInstalled("claude-agent-acp");
|
|
102
|
+
|
|
103
|
+
expect(execFileMock).toHaveBeenCalledTimes(1);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("concurrent calls dedupe to exactly one `npm i -g`", async () => {
|
|
107
|
+
execScripts.set("npm i", { stdout: "" });
|
|
108
|
+
|
|
109
|
+
const [a, b, c] = await Promise.all([
|
|
110
|
+
ensureAdapterInstalled("claude-agent-acp"),
|
|
111
|
+
ensureAdapterInstalled("claude-agent-acp"),
|
|
112
|
+
ensureAdapterInstalled("claude-agent-acp"),
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
expect(a.installed).toBe(true);
|
|
116
|
+
expect(b.installed).toBe(true);
|
|
117
|
+
expect(c.installed).toBe(true);
|
|
118
|
+
expect(execFileMock).toHaveBeenCalledTimes(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("different commands install independently", async () => {
|
|
122
|
+
execScripts.set("npm i", { stdout: "" });
|
|
123
|
+
|
|
124
|
+
const [claude, codex] = await Promise.all([
|
|
125
|
+
ensureAdapterInstalled("claude-agent-acp"),
|
|
126
|
+
ensureAdapterInstalled("codex-acp"),
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
expect(claude.installed).toBe(true);
|
|
130
|
+
expect(codex.installed).toBe(true);
|
|
131
|
+
expect(execFileMock).toHaveBeenCalledTimes(2);
|
|
132
|
+
const installedPackages = execFileMock.mock.calls.map(
|
|
133
|
+
(call) => (call[1] as string[])[2],
|
|
134
|
+
);
|
|
135
|
+
expect(installedPackages.sort()).toEqual([
|
|
136
|
+
"@agentclientprotocol/claude-agent-acp",
|
|
137
|
+
"@zed-industries/codex-acp",
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("resolveAgentWithAutoInstall - resolution order", () => {
|
|
143
|
+
test("binary missing + bun present: resolves via bunx without invoking npm", async () => {
|
|
144
|
+
which.setWhich({ bun: "/usr/local/bin/bun" });
|
|
145
|
+
|
|
146
|
+
const result = await resolveAgentWithAutoInstall("claude");
|
|
147
|
+
|
|
148
|
+
expect(result.resolved.ok).toBe(true);
|
|
149
|
+
if (!result.resolved.ok) return;
|
|
150
|
+
expect(result.resolved.agent.command).toBe("bun");
|
|
151
|
+
expect(result.resolved.agent.adapterCommand).toBe("claude-agent-acp");
|
|
152
|
+
expect(result.autoInstalledPackage).toBeUndefined();
|
|
153
|
+
expect(result.failureMessage).toBeUndefined();
|
|
154
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("binary + bun missing, npm install succeeds: falls back to the npm flow", async () => {
|
|
158
|
+
let installed = false;
|
|
159
|
+
which.setWhich((cmd) =>
|
|
160
|
+
installed && cmd === "claude-agent-acp"
|
|
161
|
+
? "/usr/local/bin/claude-agent-acp"
|
|
162
|
+
: null,
|
|
163
|
+
);
|
|
164
|
+
execScripts.set("npm i", {
|
|
165
|
+
stdout: "",
|
|
166
|
+
onCall: () => {
|
|
167
|
+
installed = true;
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const result = await resolveAgentWithAutoInstall("claude");
|
|
172
|
+
|
|
173
|
+
expect(result.resolved.ok).toBe(true);
|
|
174
|
+
if (!result.resolved.ok) return;
|
|
175
|
+
expect(result.resolved.agent.command).toBe("claude-agent-acp");
|
|
176
|
+
expect(result.autoInstalledPackage).toBe(
|
|
177
|
+
"@agentclientprotocol/claude-agent-acp",
|
|
178
|
+
);
|
|
179
|
+
expect(execFileMock).toHaveBeenCalledTimes(1);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("binary, bun, and npm all missing: failure message carries the hint and install error", async () => {
|
|
183
|
+
execScripts.set("npm i", { error: new Error("spawn npm ENOENT") });
|
|
184
|
+
|
|
185
|
+
const result = await resolveAgentWithAutoInstall("claude");
|
|
186
|
+
|
|
187
|
+
expect(result.resolved.ok).toBe(false);
|
|
188
|
+
expect(result.failureMessage).toContain(
|
|
189
|
+
"claude-agent-acp is not on PATH",
|
|
190
|
+
);
|
|
191
|
+
expect(result.failureMessage).toContain(
|
|
192
|
+
"npm i -g @agentclientprotocol/claude-agent-acp",
|
|
193
|
+
);
|
|
194
|
+
expect(result.failureMessage).toContain("ENOENT");
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Silent auto-install for known ACP adapter binaries.
|
|
3
|
+
*
|
|
4
|
+
* Fallback path: the resolver (`resolve-agent.ts`) already rewrites missing
|
|
5
|
+
* allowlisted adapters to run via `bun x` when `bun` is on PATH, so
|
|
6
|
+
* `binary_not_found` only reaches this module when bun is absent (or the
|
|
7
|
+
* command has no package mapping). When a spawn does fail preflight with
|
|
8
|
+
* `binary_not_found`, callers (the `acp_spawn` tool and the `/v1/acp/spawn`
|
|
9
|
+
* route) call `resolveAgentWithAutoInstall(agentId)`, which tries a global
|
|
10
|
+
* npm install of the mapped adapter package, then re-resolves and continues.
|
|
11
|
+
* On failure they fall back to the existing actionable install hint.
|
|
12
|
+
*
|
|
13
|
+
* Security boundary: only commands present in `DEFAULT_AGENT_NPM_PACKAGES`
|
|
14
|
+
* are ever installed. The package names are vendored constants, NOT user
|
|
15
|
+
* input: an arbitrary command from user config must never be turned into
|
|
16
|
+
* an `npm i -g <attacker-controlled-name>` execution.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { execFile } from "node:child_process";
|
|
20
|
+
|
|
21
|
+
import { DEFAULT_AGENT_NPM_PACKAGES } from "../config/acp-defaults.js";
|
|
22
|
+
import { getLogger } from "../util/logger.js";
|
|
23
|
+
import {
|
|
24
|
+
resolveAcpAgent,
|
|
25
|
+
type ResolveAcpAgentResult,
|
|
26
|
+
} from "./resolve-agent.js";
|
|
27
|
+
|
|
28
|
+
const log = getLogger("acp:auto-install");
|
|
29
|
+
|
|
30
|
+
/** Per-install timeout for `npm i -g`. Generous: cold npm caches are slow. */
|
|
31
|
+
const NPM_INSTALL_TIMEOUT_MS = 120_000;
|
|
32
|
+
|
|
33
|
+
export interface AdapterInstallResult {
|
|
34
|
+
installed: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Per-command install promises. Concurrent spawns for the same missing
|
|
40
|
+
* binary dedupe to a single `npm i -g`; successful results are cached for
|
|
41
|
+
* the process lifetime. Failed installs are evicted so a later spawn can
|
|
42
|
+
* retry (e.g. after the user fixes network or npm permissions).
|
|
43
|
+
*/
|
|
44
|
+
const installPromises = new Map<string, Promise<AdapterInstallResult>>();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Run `execFile` with an AbortController-driven timeout. Returns the stdout
|
|
48
|
+
* on success; throws on error or timeout. Shared by the adapter installer
|
|
49
|
+
* here and the version probes in `tools/acp/spawn.ts`.
|
|
50
|
+
*/
|
|
51
|
+
export function execFileWithTimeout(
|
|
52
|
+
command: string,
|
|
53
|
+
args: string[],
|
|
54
|
+
timeoutMs: number,
|
|
55
|
+
): Promise<string> {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
59
|
+
execFile(
|
|
60
|
+
command,
|
|
61
|
+
args,
|
|
62
|
+
{ signal: controller.signal, encoding: "utf8" },
|
|
63
|
+
(err, stdout) => {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
if (err) {
|
|
66
|
+
reject(err);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
resolve(stdout);
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Install the npm package mapped to `command` if (and only if) the command
|
|
77
|
+
* is a known adapter binary. Unknown commands resolve to
|
|
78
|
+
* `{ installed: false }` without ever invoking npm (see the security
|
|
79
|
+
* boundary note in the module doc).
|
|
80
|
+
*/
|
|
81
|
+
export function ensureAdapterInstalled(
|
|
82
|
+
command: string,
|
|
83
|
+
): Promise<AdapterInstallResult> {
|
|
84
|
+
const packageName = DEFAULT_AGENT_NPM_PACKAGES[command];
|
|
85
|
+
if (!packageName) {
|
|
86
|
+
return Promise.resolve({ installed: false });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const inFlight = installPromises.get(command);
|
|
90
|
+
if (inFlight) return inFlight;
|
|
91
|
+
|
|
92
|
+
const promise = runInstall(command, packageName).then((result) => {
|
|
93
|
+
if (!result.installed) installPromises.delete(command);
|
|
94
|
+
return result;
|
|
95
|
+
});
|
|
96
|
+
installPromises.set(command, promise);
|
|
97
|
+
return promise;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function runInstall(
|
|
101
|
+
command: string,
|
|
102
|
+
packageName: string,
|
|
103
|
+
): Promise<AdapterInstallResult> {
|
|
104
|
+
log.info({ command, packageName }, "Auto-installing missing ACP adapter");
|
|
105
|
+
try {
|
|
106
|
+
await execFileWithTimeout(
|
|
107
|
+
"npm",
|
|
108
|
+
["i", "-g", packageName],
|
|
109
|
+
NPM_INSTALL_TIMEOUT_MS,
|
|
110
|
+
);
|
|
111
|
+
log.info({ command, packageName }, "ACP adapter auto-install succeeded");
|
|
112
|
+
return { installed: true };
|
|
113
|
+
} catch (err) {
|
|
114
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
115
|
+
log.warn(
|
|
116
|
+
{ err, command, packageName },
|
|
117
|
+
"ACP adapter auto-install failed (falling back to install hint)",
|
|
118
|
+
);
|
|
119
|
+
return { installed: false, error };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface ResolveWithAutoInstallResult {
|
|
124
|
+
/** The final resolver outcome (post-install re-resolve when applicable). */
|
|
125
|
+
resolved: ResolveAcpAgentResult;
|
|
126
|
+
/** Set when a missing adapter binary was silently installed via npm. */
|
|
127
|
+
autoInstalledPackage?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Set when the auto-install itself failed: the original install hint
|
|
130
|
+
* augmented with the npm failure reason. Callers should surface this
|
|
131
|
+
* instead of re-deriving a message from `resolved`.
|
|
132
|
+
*/
|
|
133
|
+
failureMessage?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Resolve an ACP agent id, silently auto-installing the mapped adapter
|
|
138
|
+
* package when (and only when) the failure is a missing allowlisted binary.
|
|
139
|
+
* Shared by the `acp_spawn` tool and the `/v1/acp/spawn` route so the
|
|
140
|
+
* resolve-install-re-resolve flow has a single implementation; callers map
|
|
141
|
+
* the result to their transport (tool error result vs. HTTP error class).
|
|
142
|
+
*/
|
|
143
|
+
export async function resolveAgentWithAutoInstall(
|
|
144
|
+
agentId: string,
|
|
145
|
+
): Promise<ResolveWithAutoInstallResult> {
|
|
146
|
+
const resolved = resolveAcpAgent(agentId);
|
|
147
|
+
if (resolved.ok || resolved.reason !== "binary_not_found") {
|
|
148
|
+
return { resolved };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const { command, hint } = resolved;
|
|
152
|
+
const install = await ensureAdapterInstalled(command);
|
|
153
|
+
if (install.installed) {
|
|
154
|
+
const retried = resolveAcpAgent(agentId);
|
|
155
|
+
if (retried.ok) {
|
|
156
|
+
log.info(
|
|
157
|
+
{ agentId, command },
|
|
158
|
+
"Auto-installed missing ACP adapter binary",
|
|
159
|
+
);
|
|
160
|
+
return {
|
|
161
|
+
resolved: retried,
|
|
162
|
+
autoInstalledPackage: DEFAULT_AGENT_NPM_PACKAGES[command],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
} else if (install.error) {
|
|
166
|
+
return {
|
|
167
|
+
resolved,
|
|
168
|
+
failureMessage: `${command} is not on PATH. ${hint} (auto-install failed: ${install.error})`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return { resolved };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @internal: exposed for tests only. */
|
|
175
|
+
export function _resetAdapterInstallCacheForTests(): void {
|
|
176
|
+
installPromises.clear();
|
|
177
|
+
}
|
|
@@ -51,6 +51,7 @@ interface TerminalState {
|
|
|
51
51
|
export class VellumAcpClientHandler implements Client {
|
|
52
52
|
private terminals = new Map<string, TerminalState>();
|
|
53
53
|
private accumulatedText = "";
|
|
54
|
+
private suppressForwarding = false;
|
|
54
55
|
/** Tracks pending ACP permission requestIds for cleanup on session close. */
|
|
55
56
|
readonly pendingRequestIds = new Set<string>();
|
|
56
57
|
|
|
@@ -65,8 +66,38 @@ export class VellumAcpClientHandler implements Client {
|
|
|
65
66
|
private readonly parentConversationId: string,
|
|
66
67
|
) {}
|
|
67
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Begins suppressing session updates from being forwarded to Vellum.
|
|
71
|
+
*
|
|
72
|
+
* Per the ACP spec, `session/load` replays the entire conversation history
|
|
73
|
+
* as `session/update` notifications before the load response resolves. The
|
|
74
|
+
* parent conversation already received those events during the original
|
|
75
|
+
* run, so re-forwarding them would duplicate them into the conversation and
|
|
76
|
+
* the ring buffer. Callers wrap `loadSession` in
|
|
77
|
+
* beginReplaySuppression()/endReplaySuppression() to drop the replay.
|
|
78
|
+
* `session/resume` performs no replay, which is why it is preferred when
|
|
79
|
+
* the agent supports it.
|
|
80
|
+
*/
|
|
81
|
+
beginReplaySuppression(): void {
|
|
82
|
+
this.suppressForwarding = true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Ends replay suppression; subsequent updates flow normally. */
|
|
86
|
+
endReplaySuppression(): void {
|
|
87
|
+
this.suppressForwarding = false;
|
|
88
|
+
}
|
|
89
|
+
|
|
68
90
|
async sessionUpdate(params: SessionNotification): Promise<void> {
|
|
69
91
|
const update = params.update;
|
|
92
|
+
|
|
93
|
+
if (this.suppressForwarding) {
|
|
94
|
+
log.debug(
|
|
95
|
+
{ acpSessionId: this.acpSessionId, updateType: update.sessionUpdate },
|
|
96
|
+
"Dropping replayed session update during suppression",
|
|
97
|
+
);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
70
101
|
log.debug(
|
|
71
102
|
{ acpSessionId: this.acpSessionId, updateType: update.sessionUpdate },
|
|
72
103
|
"ACP session update received",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the ACP feature gate.
|
|
3
|
+
*
|
|
4
|
+
* `isAcpEnabled` is an OR of the `acp` feature flag and the legacy
|
|
5
|
+
* `config.acp.enabled` field: either switch enables the subsystem, and
|
|
6
|
+
* neither implicitly flips the other.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
import { setOverridesForTesting } from "../__tests__/feature-flag-test-helpers.js";
|
|
12
|
+
import type { AssistantConfig } from "../config/schema.js";
|
|
13
|
+
import { ACP_FLAG_KEY, isAcpEnabled } from "./feature-gate.js";
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
setOverridesForTesting({});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
setOverridesForTesting({});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/** Minimal AssistantConfig carrying only the `acp` section the gate reads. */
|
|
24
|
+
function makeConfig(acpEnabled: boolean): AssistantConfig {
|
|
25
|
+
return {
|
|
26
|
+
acp: { enabled: acpEnabled, maxConcurrentSessions: 4, agents: {} },
|
|
27
|
+
} as AssistantConfig;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("isAcpEnabled", () => {
|
|
31
|
+
test("returns false when both the flag and config.acp.enabled are off", () => {
|
|
32
|
+
expect(isAcpEnabled(makeConfig(false))).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("returns true when config.acp.enabled is true and the flag is off", () => {
|
|
36
|
+
expect(isAcpEnabled(makeConfig(true))).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("returns true when the flag is on and config.acp.enabled is false", () => {
|
|
40
|
+
setOverridesForTesting({ [ACP_FLAG_KEY]: true });
|
|
41
|
+
expect(isAcpEnabled(makeConfig(false))).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("explicit flag-off override does not defeat config.acp.enabled", () => {
|
|
45
|
+
setOverridesForTesting({ [ACP_FLAG_KEY]: false });
|
|
46
|
+
expect(isAcpEnabled(makeConfig(true))).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP (Agent Client Protocol) feature gate.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for whether the ACP subsystem is enabled. Modeled
|
|
5
|
+
* on `credential-execution/feature-gates.ts`: the flag key is declared in
|
|
6
|
+
* `meta/feature-flags/feature-flag-registry.json` and resolved through the
|
|
7
|
+
* unified feature-flag resolver.
|
|
8
|
+
*
|
|
9
|
+
* The gate is an OR of two independent switches:
|
|
10
|
+
* 1. `config.acp.enabled` — the original workspace config field. Preserved
|
|
11
|
+
* so existing workspaces with `acp.enabled: true` keep working without
|
|
12
|
+
* any migration.
|
|
13
|
+
* 2. The `acp` feature flag — adds a UI toggle. Flag state is persisted by
|
|
14
|
+
* the gateway and hot-refreshed in the daemon via the
|
|
15
|
+
* `feature_flags_changed` SSE event, so toggling it takes effect
|
|
16
|
+
* without a restart or config-file edit.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
20
|
+
import type { AssistantConfig } from "../config/schema.js";
|
|
21
|
+
|
|
22
|
+
/** Gate for the ACP coding-agent subsystem (must match the registry). */
|
|
23
|
+
export const ACP_FLAG_KEY = "acp" as const;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether ACP agent spawning/steering is enabled, via either the legacy
|
|
27
|
+
* `acp.enabled` config field or the `acp` feature flag (see module doc).
|
|
28
|
+
*/
|
|
29
|
+
export function isAcpEnabled(config: AssistantConfig): boolean {
|
|
30
|
+
return (
|
|
31
|
+
config.acp?.enabled === true ||
|
|
32
|
+
isAssistantFeatureFlagEnabled(ACP_FLAG_KEY, config)
|
|
33
|
+
);
|
|
34
|
+
}
|