@vellumai/assistant 0.8.8 → 0.8.9-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/ARCHITECTURE.md +6 -6
- package/bun.lock +2 -2
- package/examples/plugins/echo/README.md +61 -60
- 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/skill-host.ts +7 -6
- package/openapi.yaml +235 -6
- package/package.json +2 -2
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
- package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
- package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
- package/src/__tests__/agent-loop-thinking.test.ts +36 -20
- package/src/__tests__/agent-loop.test.ts +441 -96
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
- package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
- package/src/__tests__/anthropic-provider.test.ts +1 -1
- package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +132 -0
- package/src/__tests__/approval-cascade.test.ts +5 -4
- package/src/__tests__/approval-routes-http.test.ts +4 -1
- package/src/__tests__/background-workers-disk-pressure.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__/compaction-circuit.test.ts +258 -0
- package/src/__tests__/compaction-direct.test.ts +132 -0
- package/src/__tests__/compaction-events.test.ts +5 -5
- package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
- package/src/__tests__/context-overflow-reducer.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +142 -218
- package/src/__tests__/conversation-agent-loop.test.ts +297 -586
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
- package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
- package/src/__tests__/conversation-process-callsite.test.ts +14 -14
- package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
- package/src/__tests__/conversation-queue.test.ts +9 -9
- package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
- package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
- package/src/__tests__/conversation-slash-queue.test.ts +6 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
- package/src/__tests__/conversation-speed-override.test.ts +10 -9
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
- package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/dynamic-page-surface.test.ts +31 -0
- package/src/__tests__/empty-response-hook.test.ts +1 -1
- package/src/__tests__/file-write-tool.test.ts +63 -0
- package/src/__tests__/gateway-only-guard.test.ts +12 -2
- 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__/history-repair-hook.test.ts +1 -1
- 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__/inference-profile-reaper.test.ts +62 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
- package/src/__tests__/injector-background-turn.test.ts +13 -23
- package/src/__tests__/injector-chain.test.ts +268 -44
- package/src/__tests__/injector-disk-pressure.test.ts +210 -52
- package/src/__tests__/injector-document-comments.test.ts +97 -114
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
- package/src/__tests__/injector-v3-suppression.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
- package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +131 -26
- package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
- package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
- package/src/__tests__/plugin-api-shim.test.ts +6 -9
- package/src/__tests__/plugin-bootstrap.test.ts +12 -23
- package/src/__tests__/plugin-registry.test.ts +3 -49
- package/src/__tests__/plugin-types.test.ts +0 -70
- package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
- package/src/__tests__/steer-tool-repair.test.ts +1 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-detail.test.ts +25 -7
- 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__/title-generate-hook.test.ts +1 -1
- package/src/__tests__/tool-error-hook.test.ts +1 -1
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +54 -286
- 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 +106 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +280 -0
- package/src/acp/auto-install.ts +232 -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 +80 -27
- package/src/acp/resolve-agent.test.ts +225 -9
- package/src/acp/resolve-agent.ts +122 -17
- package/src/acp/resume-hint.ts +23 -0
- package/src/acp/session-manager.ts +507 -73
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +414 -248
- package/src/api/responses/conversation-message.ts +14 -1
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
- package/src/cli/commands/plugins.ts +43 -37
- package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
- package/src/cli/lib/install-from-github.ts +464 -55
- package/src/cli/lib/plugin-catalog-cache.ts +84 -0
- package/src/cli/lib/plugin-details.ts +409 -0
- package/src/cli/lib/plugin-marketplace.ts +197 -0
- package/src/cli/lib/search-plugins.ts +195 -29
- 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/bundled-skills/acp/SKILL.md +85 -33
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
- package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
- 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/tools/app-list.ts +62 -0
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -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-tool-registry.ts +4 -0
- package/src/config/call-site-defaults.ts +0 -2
- package/src/config/feature-flag-registry.json +15 -6
- package/src/config/schemas/call-site-catalog.ts +0 -14
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +0 -2
- package/src/context/compactor.ts +22 -4
- package/src/context/strip-injections.ts +8 -2
- package/src/context/window-manager.ts +27 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
- package/src/daemon/conversation-agent-loop.ts +175 -1055
- package/src/daemon/conversation-lifecycle.ts +11 -255
- package/src/daemon/conversation-process.ts +8 -136
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +293 -392
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +24 -8
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +404 -57
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/external-plugins-bootstrap.ts +14 -19
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-proxy-preactivation.ts +1 -3
- package/src/daemon/lifecycle.ts +21 -7
- package/src/daemon/server.ts +2 -0
- package/src/daemon/wake-conversation-ops.ts +269 -0
- package/src/embedded/plugin-api.ts +2 -2
- package/src/export/__tests__/transcript-formatter.test.ts +5 -0
- 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/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.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/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
- package/src/memory/conversation-crud.ts +29 -19
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/db-init.ts +2 -0
- package/src/memory/job-handlers/conversation-starters.ts +13 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
- package/src/memory/jobs-worker.ts +25 -1
- package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
- package/src/memory/v2/consolidation-job.ts +13 -4
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +6 -5
- package/src/plugin-api/types.ts +75 -0
- package/src/plugins/defaults/compaction/compact.ts +59 -0
- package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
- package/src/plugins/defaults/compaction/manager-store.ts +57 -0
- package/src/plugins/defaults/compaction/package.json +1 -2
- package/src/plugins/defaults/empty-response/package.json +0 -1
- package/src/plugins/defaults/history-repair/package.json +0 -1
- package/src/plugins/defaults/index.ts +135 -26
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
- package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
- 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__/live-integration.test.ts +9 -6
- 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 +8 -5
- 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 +10 -5
- 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 +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
- 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/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
- 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 +5 -5
- 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 +7 -7
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
- package/src/plugins/defaults/title-generate/package.json +0 -1
- package/src/plugins/defaults/tool-error/package.json +0 -1
- package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
- package/src/plugins/pipeline.ts +6 -293
- package/src/plugins/registry.ts +9 -37
- package/src/plugins/types.ts +76 -381
- package/src/plugins/user-loader.ts +30 -127
- package/src/prompts/__tests__/system-prompt.test.ts +6 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
- package/src/runtime/__tests__/agent-wake.test.ts +555 -691
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +108 -209
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +315 -55
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
- 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/acp-routes.test.ts +89 -25
- package/src/runtime/routes/acp-routes.ts +81 -29
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +6 -10
- 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 +1 -1
- package/src/runtime/routes/conversation-routes.ts +28 -2
- package/src/runtime/routes/conversation-starter-routes.ts +13 -7
- package/src/runtime/routes/conversations-import-routes.ts +24 -7
- 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-routes.ts +18 -3
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
- package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
- package/src/runtime/routes/memory-v3-routes.ts +16 -6
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +337 -35
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/signals/cancel.ts +2 -4
- package/src/subagent/manager.ts +21 -5
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +8 -2
- package/src/tools/acp/spawn.test.ts +176 -195
- package/src/tools/acp/spawn.ts +37 -172
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +166 -50
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/ui-surface/definitions.ts +25 -5
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
- package/docs/plugins.md +0 -832
- package/examples/plugins/echo/register.ts +0 -143
- 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__/overflow-reduce-pipeline.test.ts +0 -667
- package/src/__tests__/pipeline-runner.test.ts +0 -554
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/daemon/wake-target-adapter.ts +0 -253
- 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/register.ts +0 -35
- package/src/plugins/defaults/compaction/terminal.ts +0 -73
- package/src/plugins/defaults/empty-response/register.ts +0 -23
- package/src/plugins/defaults/history-repair/register.ts +0 -24
- 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/title-generate/register.ts +0 -35
- package/src/plugins/defaults/tool-error/register.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
- package/src/plugins/external-api.ts +0 -104
- package/src/proactive-artifact/aux-message-injector.ts +0 -97
- package/src/proactive-artifact/decision.test.ts +0 -226
- package/src/proactive-artifact/decision.ts +0 -165
- package/src/proactive-artifact/index.ts +0 -7
- package/src/proactive-artifact/job.test.ts +0 -962
- package/src/proactive-artifact/job.ts +0 -372
- package/src/proactive-artifact/message-copy.ts +0 -58
- package/src/proactive-artifact/trigger-state.test.ts +0 -286
- package/src/proactive-artifact/trigger-state.ts +0 -123
- /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__/health.test.ts +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__/provider-blocks.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__/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}/health.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +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
|
@@ -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
|
+
}
|
|
@@ -28,48 +28,82 @@ import {
|
|
|
28
28
|
getCredentialMetadata,
|
|
29
29
|
upsertCredentialMetadata,
|
|
30
30
|
} from "../tools/credentials/metadata-store.js";
|
|
31
|
+
import { getLogger } from "../util/logger.js";
|
|
31
32
|
import type { AcpAgentConfig } from "./types.js";
|
|
32
33
|
|
|
34
|
+
const log = getLogger("acp:prepare-agent-env");
|
|
35
|
+
|
|
33
36
|
const ACP_SPAWN_TOOL = "acp_spawn";
|
|
37
|
+
const ACP_SERVICE = "acp";
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
|
-
* Ensure
|
|
37
|
-
*
|
|
40
|
+
* Ensure an `acp/<field>` credential has metadata that allows the
|
|
41
|
+
* `acp_spawn` tool to read it, but only for legacy/unmanaged cases:
|
|
38
42
|
*
|
|
39
|
-
* - No metadata at all
|
|
40
|
-
* - Metadata exists with an empty `allowedTools
|
|
43
|
+
* - No metadata at all: create with `allowedTools: ["acp_spawn"]`.
|
|
44
|
+
* - Metadata exists with an empty `allowedTools`: default provisioning
|
|
41
45
|
* path (user ran `credentials set` without `--allowed-tools`), add it.
|
|
42
|
-
* - Metadata exists with a non-empty `allowedTools
|
|
43
|
-
* by the user/admin. Respect it even if `acp_spawn` is absent
|
|
44
|
-
* broker will deny the read and the
|
|
46
|
+
* - Metadata exists with a non-empty `allowedTools`: explicit policy set
|
|
47
|
+
* by the user/admin. Respect it even if `acp_spawn` is absent; the
|
|
48
|
+
* broker will deny the read and the caller decides whether that's fatal.
|
|
45
49
|
*/
|
|
46
|
-
function
|
|
47
|
-
|
|
50
|
+
function ensureAcpCredentialPolicy(
|
|
51
|
+
field: string,
|
|
52
|
+
usageDescription: string,
|
|
53
|
+
): void {
|
|
54
|
+
const meta = getCredentialMetadata(ACP_SERVICE, field);
|
|
48
55
|
if (!meta) {
|
|
49
|
-
upsertCredentialMetadata(
|
|
56
|
+
upsertCredentialMetadata(ACP_SERVICE, field, {
|
|
50
57
|
allowedTools: [ACP_SPAWN_TOOL],
|
|
51
|
-
usageDescription
|
|
52
|
-
"Claude OAuth token for ACP agent authentication",
|
|
58
|
+
usageDescription,
|
|
53
59
|
});
|
|
54
60
|
return;
|
|
55
61
|
}
|
|
56
62
|
const tools = meta.allowedTools ?? [];
|
|
57
63
|
if (tools.length === 0) {
|
|
58
|
-
upsertCredentialMetadata(
|
|
64
|
+
upsertCredentialMetadata(ACP_SERVICE, field, {
|
|
59
65
|
allowedTools: [ACP_SPAWN_TOOL],
|
|
60
66
|
});
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Read an `acp/<field>` credential through the broker and inject it into
|
|
72
|
+
* `env` under `envVar`. Returns the broker's failure reason when the value
|
|
73
|
+
* was not injected (missing credential, denied policy, no stored value),
|
|
74
|
+
* or undefined on success. Never throws: `serverUse` signals every failure
|
|
75
|
+
* mode, including a simply-absent credential, as `{ success: false,
|
|
76
|
+
* reason }`, so callers choose whether a miss is fatal.
|
|
77
|
+
*/
|
|
78
|
+
async function injectCredential(
|
|
79
|
+
env: Record<string, string>,
|
|
80
|
+
field: string,
|
|
81
|
+
envVar: string,
|
|
82
|
+
usageDescription: string,
|
|
83
|
+
): Promise<string | undefined> {
|
|
84
|
+
ensureAcpCredentialPolicy(field, usageDescription);
|
|
85
|
+
const result = await credentialBroker.serverUse<void>({
|
|
86
|
+
service: ACP_SERVICE,
|
|
87
|
+
field,
|
|
88
|
+
toolName: ACP_SPAWN_TOOL,
|
|
89
|
+
execute: async (value) => {
|
|
90
|
+
env[envVar] = value;
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
return result.success ? undefined : result.reason;
|
|
94
|
+
}
|
|
95
|
+
|
|
64
96
|
/**
|
|
65
97
|
* Returns a NEW config with any required credentials merged into `env`.
|
|
66
98
|
* Does NOT mutate the input. Throws `FailedDependencyError` if a required
|
|
67
99
|
* credential is missing from both the user-supplied env override and the
|
|
68
100
|
* secure store.
|
|
69
101
|
*
|
|
70
|
-
* Gating is keyed off the resolved
|
|
71
|
-
*
|
|
72
|
-
*
|
|
102
|
+
* Gating is keyed off the resolved command basename, not the user-facing
|
|
103
|
+
* agent id. A custom `acp.agents.my-claude = { command: "claude-agent-acp",
|
|
104
|
+
* ... }` alias (or a full path like `/opt/bin/claude-agent-acp`) still gets
|
|
105
|
+
* the env it needs. Because resolution always yields the real adapter binary
|
|
106
|
+
* (never a `bun x` wrapper), the basename is the canonical adapter identity.
|
|
73
107
|
*
|
|
74
108
|
* For `claude-agent-acp` the only required env var is
|
|
75
109
|
* `CLAUDE_CODE_OAUTH_TOKEN`. Two provisioning routes converge on it, with
|
|
@@ -84,6 +118,11 @@ function ensureAcpTokenPolicy(): void {
|
|
|
84
118
|
* before spawning. The "fail-fast" throw is symmetric with the existing
|
|
85
119
|
* `binary_not_found` preflight in `resolveAcpAgent` and strictly better
|
|
86
120
|
* than a `warn` + zombie subprocess 10 seconds later.
|
|
121
|
+
*
|
|
122
|
+
* For `gemini` the env var is `GEMINI_API_KEY`, provisioned the same two
|
|
123
|
+
* ways (config.json override wins, vault field `gemini_api_key` second),
|
|
124
|
+
* but it is OPTIONAL: the Gemini CLI supports its own OAuth login, so a
|
|
125
|
+
* vault miss proceeds without the key instead of failing the spawn.
|
|
87
126
|
*/
|
|
88
127
|
export async function prepareAgentEnv(
|
|
89
128
|
agentConfig: AcpAgentConfig,
|
|
@@ -92,19 +131,16 @@ export async function prepareAgentEnv(
|
|
|
92
131
|
// agent reference. The local `env` binding sidesteps TS narrowing
|
|
93
132
|
// limitations on the optional `AcpAgentConfig.env` field.
|
|
94
133
|
const env: Record<string, string> = { ...(agentConfig.env ?? {}) };
|
|
95
|
-
const
|
|
134
|
+
const adapterCommand = basename(agentConfig.command);
|
|
96
135
|
|
|
97
|
-
if (
|
|
136
|
+
if (adapterCommand === "claude-agent-acp") {
|
|
98
137
|
if (!env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
env.CLAUDE_CODE_OAUTH_TOKEN = token;
|
|
106
|
-
},
|
|
107
|
-
});
|
|
138
|
+
await injectCredential(
|
|
139
|
+
env,
|
|
140
|
+
"claude_oauth_token",
|
|
141
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
142
|
+
"Claude OAuth token for ACP agent authentication",
|
|
143
|
+
);
|
|
108
144
|
}
|
|
109
145
|
if (!env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
110
146
|
throw new FailedDependencyError(
|
|
@@ -113,6 +149,23 @@ export async function prepareAgentEnv(
|
|
|
113
149
|
"(or set it under acp.agents.<id>.env in config.json).",
|
|
114
150
|
);
|
|
115
151
|
}
|
|
152
|
+
} else if (adapterCommand === "gemini") {
|
|
153
|
+
if (!env.GEMINI_API_KEY) {
|
|
154
|
+
const missReason = await injectCredential(
|
|
155
|
+
env,
|
|
156
|
+
"gemini_api_key",
|
|
157
|
+
"GEMINI_API_KEY",
|
|
158
|
+
"Gemini API key for ACP agent authentication",
|
|
159
|
+
);
|
|
160
|
+
if (missReason !== undefined) {
|
|
161
|
+
// Optional credential: Gemini CLI can authenticate via its own
|
|
162
|
+
// OAuth login, so a vault miss must not fail the spawn.
|
|
163
|
+
log.debug(
|
|
164
|
+
{ reason: missReason },
|
|
165
|
+
"Gemini API key unavailable from the vault; spawning without GEMINI_API_KEY",
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
116
169
|
}
|
|
117
170
|
|
|
118
171
|
return { ...agentConfig, env };
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import { setOverridesForTesting } from "../__tests__/feature-flag-test-helpers.js";
|
|
3
4
|
import { installAcpConfigStub } from "./__tests__/helpers/acp-config-stub.js";
|
|
4
5
|
import { installWhichStub } from "./__tests__/helpers/which-stub.js";
|
|
6
|
+
import { ACP_FLAG_KEY } from "./feature-gate.js";
|
|
5
7
|
|
|
6
8
|
const config = await installAcpConfigStub();
|
|
7
9
|
const which = installWhichStub();
|
|
8
10
|
|
|
9
11
|
afterAll(() => {
|
|
10
12
|
which.restore();
|
|
13
|
+
setOverridesForTesting({});
|
|
11
14
|
});
|
|
12
15
|
|
|
13
16
|
const { resolveAcpAgent, listAcpAgents } = await import("./resolve-agent.js");
|
|
14
17
|
|
|
15
18
|
beforeEach(() => {
|
|
16
19
|
config.setConfig({});
|
|
20
|
+
// Default: no flag overrides, so the `acp` flag falls back to its registry
|
|
21
|
+
// default (false) and enablement comes from the config stub alone.
|
|
22
|
+
setOverridesForTesting({});
|
|
17
23
|
// Default: every command on PATH so binary preflight passes unless a test
|
|
18
24
|
// explicitly says otherwise.
|
|
19
25
|
which.setWhich((cmd) => `/usr/local/bin/${cmd}`);
|
|
@@ -24,7 +30,7 @@ beforeEach(() => {
|
|
|
24
30
|
// ---------------------------------------------------------------------------
|
|
25
31
|
|
|
26
32
|
describe("resolveAcpAgent", () => {
|
|
27
|
-
test("returns acp_disabled when config.acp.enabled
|
|
33
|
+
test("returns acp_disabled when both the feature flag and config.acp.enabled are off", () => {
|
|
28
34
|
config.setConfig({ enabled: false });
|
|
29
35
|
|
|
30
36
|
const result = resolveAcpAgent("claude");
|
|
@@ -33,10 +39,23 @@ describe("resolveAcpAgent", () => {
|
|
|
33
39
|
if (result.ok) return;
|
|
34
40
|
expect(result.reason).toBe("acp_disabled");
|
|
35
41
|
if (result.reason !== "acp_disabled") return;
|
|
42
|
+
expect(result.hint).toContain("ACP Coding Agents");
|
|
43
|
+
expect(result.hint).toContain("feature flag");
|
|
36
44
|
expect(result.hint).toContain("acp.enabled");
|
|
37
45
|
expect(result.hint).toContain("config.json");
|
|
38
46
|
});
|
|
39
47
|
|
|
48
|
+
test("resolution proceeds when the acp feature flag is on and config.acp.enabled is false", () => {
|
|
49
|
+
config.setConfig({ enabled: false });
|
|
50
|
+
setOverridesForTesting({ [ACP_FLAG_KEY]: true });
|
|
51
|
+
|
|
52
|
+
const result = resolveAcpAgent("claude");
|
|
53
|
+
|
|
54
|
+
expect(result.ok).toBe(true);
|
|
55
|
+
if (!result.ok) return;
|
|
56
|
+
expect(result.agent.command).toBe("claude-agent-acp");
|
|
57
|
+
});
|
|
58
|
+
|
|
40
59
|
test("user config wins over default profile", () => {
|
|
41
60
|
config.setConfig({
|
|
42
61
|
agents: {
|
|
@@ -78,6 +97,79 @@ describe("resolveAcpAgent", () => {
|
|
|
78
97
|
expect(result.agent.command).toBe("claude-agent-acp");
|
|
79
98
|
});
|
|
80
99
|
|
|
100
|
+
test("falls back to the gemini default profile when no user entry", () => {
|
|
101
|
+
config.setConfig({ agents: {} });
|
|
102
|
+
|
|
103
|
+
const result = resolveAcpAgent("gemini");
|
|
104
|
+
|
|
105
|
+
expect(result.ok).toBe(true);
|
|
106
|
+
if (!result.ok) return;
|
|
107
|
+
expect(result.agent.command).toBe("gemini");
|
|
108
|
+
expect(result.agent.args).toEqual(["--acp"]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test.each([
|
|
112
|
+
["claude code", "claude-agent-acp"],
|
|
113
|
+
["Claude Code", "claude-agent-acp"],
|
|
114
|
+
["claude-code", "claude-agent-acp"],
|
|
115
|
+
["claude_code", "claude-agent-acp"],
|
|
116
|
+
["codex cli", "codex-acp"],
|
|
117
|
+
["OpenAI Codex", "codex-acp"],
|
|
118
|
+
["gemini cli", "gemini"],
|
|
119
|
+
["Gemini CLI", "gemini"],
|
|
120
|
+
["google gemini", "gemini"],
|
|
121
|
+
])("alias %p resolves to the %p profile", (alias, command) => {
|
|
122
|
+
config.setConfig({ agents: {} });
|
|
123
|
+
|
|
124
|
+
const result = resolveAcpAgent(alias);
|
|
125
|
+
|
|
126
|
+
expect(result.ok).toBe(true);
|
|
127
|
+
if (!result.ok) return;
|
|
128
|
+
expect(result.agent.command).toBe(command);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("user config entry literally keyed 'claude code' beats the alias", () => {
|
|
132
|
+
config.setConfig({
|
|
133
|
+
agents: {
|
|
134
|
+
"claude code": {
|
|
135
|
+
command: "my-claude-fork",
|
|
136
|
+
args: [],
|
|
137
|
+
description: "user-defined agent that happens to share an alias",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const result = resolveAcpAgent("claude code");
|
|
143
|
+
|
|
144
|
+
expect(result.ok).toBe(true);
|
|
145
|
+
if (!result.ok) return;
|
|
146
|
+
expect(result.agent.command).toBe("my-claude-fork");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("alias re-runs the normal lookup, so a user override of the canonical id wins", () => {
|
|
150
|
+
config.setConfig({
|
|
151
|
+
agents: {
|
|
152
|
+
claude: { command: "my-custom-claude", args: [] },
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const result = resolveAcpAgent("claude code");
|
|
157
|
+
|
|
158
|
+
expect(result.ok).toBe(true);
|
|
159
|
+
if (!result.ok) return;
|
|
160
|
+
expect(result.agent.command).toBe("my-custom-claude");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("non-alias unknown id still returns unknown_agent", () => {
|
|
164
|
+
config.setConfig({ agents: {} });
|
|
165
|
+
|
|
166
|
+
const result = resolveAcpAgent("cursor cli");
|
|
167
|
+
|
|
168
|
+
expect(result.ok).toBe(false);
|
|
169
|
+
if (result.ok) return;
|
|
170
|
+
expect(result.reason).toBe("unknown_agent");
|
|
171
|
+
});
|
|
172
|
+
|
|
81
173
|
test("returns unknown_agent with merged available list when id not found", () => {
|
|
82
174
|
config.setConfig({
|
|
83
175
|
agents: {
|
|
@@ -92,7 +184,12 @@ describe("resolveAcpAgent", () => {
|
|
|
92
184
|
expect(result.reason).toBe("unknown_agent");
|
|
93
185
|
if (result.reason !== "unknown_agent") return;
|
|
94
186
|
// Defaults plus user-only ids, deduped, in stable order (defaults first).
|
|
95
|
-
expect(result.available).toEqual([
|
|
187
|
+
expect(result.available).toEqual([
|
|
188
|
+
"claude",
|
|
189
|
+
"codex",
|
|
190
|
+
"gemini",
|
|
191
|
+
"user-only",
|
|
192
|
+
]);
|
|
96
193
|
});
|
|
97
194
|
|
|
98
195
|
test("unknown_agent available list contains both defaults when user config is empty", () => {
|
|
@@ -106,6 +203,7 @@ describe("resolveAcpAgent", () => {
|
|
|
106
203
|
if (result.reason !== "unknown_agent") return;
|
|
107
204
|
expect(result.available).toContain("claude");
|
|
108
205
|
expect(result.available).toContain("codex");
|
|
206
|
+
expect(result.available).toContain("gemini");
|
|
109
207
|
});
|
|
110
208
|
|
|
111
209
|
test("returns binary_not_found with the registered install hint", () => {
|
|
@@ -118,7 +216,7 @@ describe("resolveAcpAgent", () => {
|
|
|
118
216
|
if (result.ok) return;
|
|
119
217
|
expect(result.reason).toBe("binary_not_found");
|
|
120
218
|
if (result.reason !== "binary_not_found") return;
|
|
121
|
-
expect(result.hint).toBe("
|
|
219
|
+
expect(result.hint).toBe("bun add -g @agentclientprotocol/claude-agent-acp");
|
|
122
220
|
expect(result.command).toBe("claude-agent-acp");
|
|
123
221
|
});
|
|
124
222
|
|
|
@@ -158,7 +256,7 @@ describe("resolveAcpAgent", () => {
|
|
|
158
256
|
if (result.ok) return;
|
|
159
257
|
expect(result.reason).toBe("binary_not_found");
|
|
160
258
|
if (result.reason !== "binary_not_found") return;
|
|
161
|
-
expect(result.hint).toBe("
|
|
259
|
+
expect(result.hint).toBe("bun add -g @zed-industries/codex-acp");
|
|
162
260
|
});
|
|
163
261
|
|
|
164
262
|
test("binary preflight honors agent.env.PATH override (matches spawn env)", () => {
|
|
@@ -200,6 +298,60 @@ describe("resolveAcpAgent", () => {
|
|
|
200
298
|
if (!result.ok) return;
|
|
201
299
|
expect(result.agent.args).toEqual(["--verbose"]);
|
|
202
300
|
});
|
|
301
|
+
|
|
302
|
+
test("resolves a full-path command directly without rewriting it", () => {
|
|
303
|
+
config.setConfig({
|
|
304
|
+
agents: {
|
|
305
|
+
custom: { command: "/opt/bin/claude-agent-acp", args: [] },
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const direct = resolveAcpAgent("claude");
|
|
310
|
+
expect(direct.ok).toBe(true);
|
|
311
|
+
if (!direct.ok) return;
|
|
312
|
+
expect(direct.agent.command).toBe("claude-agent-acp");
|
|
313
|
+
|
|
314
|
+
const fullPath = resolveAcpAgent("custom");
|
|
315
|
+
expect(fullPath.ok).toBe(true);
|
|
316
|
+
if (!fullPath.ok) return;
|
|
317
|
+
expect(fullPath.agent.command).toBe("/opt/bin/claude-agent-acp");
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
// resolveAcpAgent - missing binaries are never run from the task cwd
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
describe("resolveAcpAgent - missing binary", () => {
|
|
326
|
+
test("binary missing + bun present: still binary_not_found (no bunx rewrite)", () => {
|
|
327
|
+
// bun on PATH no longer makes a missing adapter "runnable" at resolve
|
|
328
|
+
// time: the sandboxed install happens separately, never as a `bun x`
|
|
329
|
+
// rewrite in the untrusted cwd.
|
|
330
|
+
config.setConfig({ agents: {} });
|
|
331
|
+
which.setWhich({ bun: "/usr/local/bin/bun" });
|
|
332
|
+
|
|
333
|
+
const result = resolveAcpAgent("claude");
|
|
334
|
+
|
|
335
|
+
expect(result.ok).toBe(false);
|
|
336
|
+
if (result.ok) return;
|
|
337
|
+
expect(result.reason).toBe("binary_not_found");
|
|
338
|
+
if (result.reason !== "binary_not_found") return;
|
|
339
|
+
expect(result.command).toBe("claude-agent-acp");
|
|
340
|
+
expect(result.hint).toBe("bun add -g @agentclientprotocol/claude-agent-acp");
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("binary missing + bun missing: binary_not_found with the bun hint", () => {
|
|
344
|
+
config.setConfig({ agents: {} });
|
|
345
|
+
which.setWhich({});
|
|
346
|
+
|
|
347
|
+
const result = resolveAcpAgent("claude");
|
|
348
|
+
|
|
349
|
+
expect(result.ok).toBe(false);
|
|
350
|
+
if (result.ok) return;
|
|
351
|
+
expect(result.reason).toBe("binary_not_found");
|
|
352
|
+
if (result.reason !== "binary_not_found") return;
|
|
353
|
+
expect(result.hint).toBe("bun add -g @agentclientprotocol/claude-agent-acp");
|
|
354
|
+
});
|
|
203
355
|
});
|
|
204
356
|
|
|
205
357
|
// ---------------------------------------------------------------------------
|
|
@@ -207,7 +359,7 @@ describe("resolveAcpAgent", () => {
|
|
|
207
359
|
// ---------------------------------------------------------------------------
|
|
208
360
|
|
|
209
361
|
describe("listAcpAgents", () => {
|
|
210
|
-
test("returns enabled: false with empty agents when
|
|
362
|
+
test("returns enabled: false with empty agents when both the flag and config are off", () => {
|
|
211
363
|
config.setConfig({ enabled: false });
|
|
212
364
|
|
|
213
365
|
const result = listAcpAgents();
|
|
@@ -216,14 +368,28 @@ describe("listAcpAgents", () => {
|
|
|
216
368
|
expect(result.agents).toEqual([]);
|
|
217
369
|
});
|
|
218
370
|
|
|
219
|
-
test("
|
|
371
|
+
test("returns the catalog when the acp feature flag is on and config.acp.enabled is false", () => {
|
|
372
|
+
config.setConfig({ enabled: false });
|
|
373
|
+
setOverridesForTesting({ [ACP_FLAG_KEY]: true });
|
|
374
|
+
|
|
375
|
+
const result = listAcpAgents();
|
|
376
|
+
|
|
377
|
+
expect(result.enabled).toBe(true);
|
|
378
|
+
expect(result.agents.map((a) => a.id)).toEqual([
|
|
379
|
+
"claude",
|
|
380
|
+
"codex",
|
|
381
|
+
"gemini",
|
|
382
|
+
]);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("includes all bundled defaults when user config is empty", () => {
|
|
220
386
|
config.setConfig({ agents: {} });
|
|
221
387
|
|
|
222
388
|
const result = listAcpAgents();
|
|
223
389
|
|
|
224
390
|
expect(result.enabled).toBe(true);
|
|
225
391
|
const ids = result.agents.map((a) => a.id);
|
|
226
|
-
expect(ids).toEqual(["claude", "codex"]);
|
|
392
|
+
expect(ids).toEqual(["claude", "codex", "gemini"]);
|
|
227
393
|
for (const entry of result.agents) {
|
|
228
394
|
expect(entry.source).toBe("default");
|
|
229
395
|
expect(entry.available).toBe(true);
|
|
@@ -245,6 +411,7 @@ describe("listAcpAgents", () => {
|
|
|
245
411
|
which.setWhich({
|
|
246
412
|
"my-claude": "/usr/bin/my-claude",
|
|
247
413
|
"codex-acp": "/usr/bin/codex-acp",
|
|
414
|
+
gemini: "/usr/bin/gemini",
|
|
248
415
|
});
|
|
249
416
|
|
|
250
417
|
const result = listAcpAgents();
|
|
@@ -257,6 +424,27 @@ describe("listAcpAgents", () => {
|
|
|
257
424
|
expect(codex?.source).toBe("default");
|
|
258
425
|
});
|
|
259
426
|
|
|
427
|
+
test("missing binaries are listed unavailable even when bun is present", () => {
|
|
428
|
+
// bun presence no longer makes a missing adapter available: it is
|
|
429
|
+
// installed on demand at spawn time, not run via `bun x` from the cwd.
|
|
430
|
+
config.setConfig({ agents: {} });
|
|
431
|
+
which.setWhich({ bun: "/usr/local/bin/bun" });
|
|
432
|
+
|
|
433
|
+
const result = listAcpAgents();
|
|
434
|
+
|
|
435
|
+
for (const entry of result.agents) {
|
|
436
|
+
expect(entry.available).toBe(false);
|
|
437
|
+
expect(entry.unavailableReason).toBeDefined();
|
|
438
|
+
expect(entry.setupHint).toContain("bun add -g");
|
|
439
|
+
}
|
|
440
|
+
// The catalog keeps the canonical adapter commands.
|
|
441
|
+
expect(result.agents.map((a) => a.command)).toEqual([
|
|
442
|
+
"claude-agent-acp",
|
|
443
|
+
"codex-acp",
|
|
444
|
+
"gemini",
|
|
445
|
+
]);
|
|
446
|
+
});
|
|
447
|
+
|
|
260
448
|
test("unavailable agent surfaces install hint derived from DEFAULT_AGENT_NPM_PACKAGES", () => {
|
|
261
449
|
config.setConfig({ agents: {} });
|
|
262
450
|
which.setWhich({ "claude-agent-acp": "/usr/bin/claude-agent-acp" });
|
|
@@ -266,7 +454,33 @@ describe("listAcpAgents", () => {
|
|
|
266
454
|
const codex = result.agents.find((a) => a.id === "codex");
|
|
267
455
|
expect(codex?.available).toBe(false);
|
|
268
456
|
expect(codex?.unavailableReason).toBe("'codex-acp' is not on PATH");
|
|
269
|
-
expect(codex?.setupHint).toBe("
|
|
457
|
+
expect(codex?.setupHint).toBe("bun add -g @zed-industries/codex-acp");
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test("unavailable gemini surfaces the @google/gemini-cli install hint", () => {
|
|
461
|
+
config.setConfig({ agents: {} });
|
|
462
|
+
which.setWhich({
|
|
463
|
+
"claude-agent-acp": "/usr/bin/claude-agent-acp",
|
|
464
|
+
"codex-acp": "/usr/bin/codex-acp",
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const result = listAcpAgents();
|
|
468
|
+
|
|
469
|
+
const gemini = result.agents.find((a) => a.id === "gemini");
|
|
470
|
+
expect(gemini?.available).toBe(false);
|
|
471
|
+
expect(gemini?.unavailableReason).toBe("'gemini' is not on PATH");
|
|
472
|
+
expect(gemini?.setupHint).toBe("bun add -g @google/gemini-cli");
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test("aliases are resolution sugar, not catalog entries", () => {
|
|
476
|
+
config.setConfig({ agents: {} });
|
|
477
|
+
|
|
478
|
+
// "gemini cli" resolves via the alias...
|
|
479
|
+
expect(resolveAcpAgent("gemini cli").ok).toBe(true);
|
|
480
|
+
|
|
481
|
+
// ...but the catalog lists only canonical ids.
|
|
482
|
+
const ids = listAcpAgents().agents.map((a) => a.id);
|
|
483
|
+
expect(ids).toEqual(["claude", "codex", "gemini"]);
|
|
270
484
|
});
|
|
271
485
|
|
|
272
486
|
test("user-only agent appended after defaults in stable order", () => {
|
|
@@ -282,6 +496,7 @@ describe("listAcpAgents", () => {
|
|
|
282
496
|
which.setWhich({
|
|
283
497
|
"claude-agent-acp": "/x",
|
|
284
498
|
"codex-acp": "/x",
|
|
499
|
+
gemini: "/x",
|
|
285
500
|
"my-binary": "/x",
|
|
286
501
|
});
|
|
287
502
|
|
|
@@ -290,9 +505,10 @@ describe("listAcpAgents", () => {
|
|
|
290
505
|
expect(result.agents.map((a) => a.id)).toEqual([
|
|
291
506
|
"claude",
|
|
292
507
|
"codex",
|
|
508
|
+
"gemini",
|
|
293
509
|
"my-agent",
|
|
294
510
|
]);
|
|
295
|
-
const userOnly = result.agents[
|
|
511
|
+
const userOnly = result.agents[3];
|
|
296
512
|
expect(userOnly.source).toBe("config");
|
|
297
513
|
expect(userOnly.description).toBe("user-only");
|
|
298
514
|
});
|