@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
package/src/subagent/manager.ts
CHANGED
|
@@ -13,7 +13,11 @@ import { v4 as uuid } from "uuid";
|
|
|
13
13
|
import { resolveCallSiteConfig } from "../config/llm-resolver.js";
|
|
14
14
|
import { getConfig } from "../config/loader.js";
|
|
15
15
|
import { Conversation } from "../daemon/conversation.js";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
findConversation,
|
|
18
|
+
removeSubagentConversation,
|
|
19
|
+
setSubagentConversation,
|
|
20
|
+
} from "../daemon/conversation-registry.js";
|
|
17
21
|
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
18
22
|
import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
|
|
19
23
|
import { wrapWithCallSiteRouting } from "../providers/call-site-routing.js";
|
|
@@ -294,6 +298,10 @@ export class SubagentManager {
|
|
|
294
298
|
// This ensures interactive prompts (host attachment reads) fail fast.
|
|
295
299
|
conversation.updateClient(wrappedSendToClient, true);
|
|
296
300
|
conversation.setIsSubagent(true);
|
|
301
|
+
// Subagents are created as background conversations (see the
|
|
302
|
+
// `bootstrapConversation` call above) and never call `loadFromDb`, so cache
|
|
303
|
+
// the type on the live conversation directly for the runtime-assembly path.
|
|
304
|
+
conversation.conversationType = "background";
|
|
297
305
|
|
|
298
306
|
// Subagents execute as background child conversations, but their tool
|
|
299
307
|
// permissions must still be scoped to the actor that spawned them. Without
|
|
@@ -334,6 +342,10 @@ export class SubagentManager {
|
|
|
334
342
|
|
|
335
343
|
managed.conversation = conversation;
|
|
336
344
|
this.subagents.set(subagentId, managed);
|
|
345
|
+
// Index the live conversation so the per-conversation injectors (workspace
|
|
346
|
+
// context, disk-pressure warning) can resolve it by id; subagents are not
|
|
347
|
+
// in the eviction-managed conversation store.
|
|
348
|
+
setSubagentConversation(conversationRecord.id, conversation);
|
|
337
349
|
const labelKey = `${config.parentConversationId}:${config.label.toLowerCase().trim()}`;
|
|
338
350
|
if (this.labelIndex.has(labelKey)) {
|
|
339
351
|
log.warn(
|
|
@@ -695,7 +707,9 @@ export class SubagentManager {
|
|
|
695
707
|
*/
|
|
696
708
|
private releaseConversation(managed: ManagedSubagent): void {
|
|
697
709
|
if (!managed.conversation) return;
|
|
698
|
-
managed.conversation
|
|
710
|
+
const conversation = managed.conversation;
|
|
711
|
+
removeSubagentConversation(conversation.conversationId, conversation);
|
|
712
|
+
conversation.dispose();
|
|
699
713
|
managed.conversation = null;
|
|
700
714
|
managed.retainedUntil = Date.now() + TERMINAL_RETENTION_MS;
|
|
701
715
|
this.ensureSweepRunning();
|
|
@@ -716,16 +730,18 @@ export class SubagentManager {
|
|
|
716
730
|
if (!managed) return;
|
|
717
731
|
|
|
718
732
|
if (managed.conversation) {
|
|
733
|
+
const conversation = managed.conversation;
|
|
719
734
|
if (!TERMINAL_STATUSES.has(managed.state.status)) {
|
|
720
|
-
|
|
735
|
+
conversation.abort(
|
|
721
736
|
createAbortReason(
|
|
722
737
|
"subagent_aborted",
|
|
723
738
|
"SubagentManager.dispose",
|
|
724
|
-
|
|
739
|
+
conversation.conversationId,
|
|
725
740
|
),
|
|
726
741
|
);
|
|
727
742
|
}
|
|
728
|
-
|
|
743
|
+
removeSubagentConversation(conversation.conversationId, conversation);
|
|
744
|
+
conversation.dispose();
|
|
729
745
|
managed.conversation = null;
|
|
730
746
|
}
|
|
731
747
|
this.subagents.delete(subagentId);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ToolContext accessors for the ACP tools.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ServerMessage } from "../../daemon/message-protocol.js";
|
|
6
|
+
import type { ToolContext } from "../types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Narrows `ToolContext.sendToClient` to the `ServerMessage` sender the ACP
|
|
10
|
+
* session manager expects. ToolContext types the callback against an
|
|
11
|
+
* index-signature message shape (`{ type: string; [key: string]: unknown }`)
|
|
12
|
+
* that ServerMessage's union members don't structurally satisfy, so the
|
|
13
|
+
* cast lives here once instead of being duplicated at every ACP tool call
|
|
14
|
+
* site.
|
|
15
|
+
*/
|
|
16
|
+
export function getSendToClient(
|
|
17
|
+
context: ToolContext,
|
|
18
|
+
): ((msg: ServerMessage) => void) | undefined {
|
|
19
|
+
return context.sendToClient as ((msg: ServerMessage) => void) | undefined;
|
|
20
|
+
}
|
|
@@ -47,7 +47,7 @@ describe("executeAcpListAgents", () => {
|
|
|
47
47
|
expect(parsed.hint).toContain("config.json");
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
test("enabled, no user config:
|
|
50
|
+
test("enabled, no user config: all defaults present with source 'default' and available based on Bun.which", async () => {
|
|
51
51
|
config.setConfig({ agents: {} });
|
|
52
52
|
|
|
53
53
|
const result = await executeAcpListAgents({}, makeContext());
|
|
@@ -58,6 +58,7 @@ describe("executeAcpListAgents", () => {
|
|
|
58
58
|
expect(parsed.agents.map((a: { id: string }) => a.id)).toEqual([
|
|
59
59
|
"claude",
|
|
60
60
|
"codex",
|
|
61
|
+
"gemini",
|
|
61
62
|
]);
|
|
62
63
|
for (const entry of parsed.agents) {
|
|
63
64
|
expect(entry.source).toBe("default");
|
|
@@ -106,7 +107,12 @@ describe("executeAcpListAgents", () => {
|
|
|
106
107
|
const codex = parsed.agents.find((a: { id: string }) => a.id === "codex");
|
|
107
108
|
expect(codex.available).toBe(false);
|
|
108
109
|
expect(codex.unavailableReason).toBe("'codex-acp' is not on PATH");
|
|
109
|
-
expect(codex.setupHint).toBe("
|
|
110
|
+
expect(codex.setupHint).toBe("bun add -g @zed-industries/codex-acp");
|
|
111
|
+
|
|
112
|
+
const gemini = parsed.agents.find((a: { id: string }) => a.id === "gemini");
|
|
113
|
+
expect(gemini.available).toBe(false);
|
|
114
|
+
expect(gemini.unavailableReason).toBe("'gemini' is not on PATH");
|
|
115
|
+
expect(gemini.setupHint).toBe("bun add -g @google/gemini-cli");
|
|
110
116
|
|
|
111
117
|
const claude = parsed.agents.find((a: { id: string }) => a.id === "claude");
|
|
112
118
|
expect(claude.available).toBe(true);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as realChildProcess from "node:child_process";
|
|
2
1
|
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
3
2
|
|
|
4
3
|
import { installAcpConfigStub } from "../../acp/__tests__/helpers/acp-config-stub.js";
|
|
4
|
+
import { installExecFileStub } from "../../acp/__tests__/helpers/exec-file-stub.js";
|
|
5
5
|
import { installWhichStub } from "../../acp/__tests__/helpers/which-stub.js";
|
|
6
6
|
import type { ToolContext } from "../types.js";
|
|
7
7
|
|
|
@@ -9,58 +9,18 @@ import type { ToolContext } from "../types.js";
|
|
|
9
9
|
// Mock infrastructure
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
interface ExecScript {
|
|
19
|
-
/** When set, the call rejects with this error. */
|
|
20
|
-
error?: Error;
|
|
21
|
-
/** When set, the call resolves with this stdout. */
|
|
22
|
-
stdout?: string;
|
|
23
|
-
}
|
|
12
|
+
const {
|
|
13
|
+
execScripts,
|
|
14
|
+
execFileMock,
|
|
15
|
+
reset: resetExecFileStub,
|
|
16
|
+
} = installExecFileStub();
|
|
24
17
|
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*/
|
|
29
|
-
const execScripts: Map<string, ExecScript> = new Map();
|
|
30
|
-
|
|
31
|
-
const execFileMock = mock(
|
|
32
|
-
(
|
|
33
|
-
command: string,
|
|
34
|
-
args: string[],
|
|
35
|
-
_options: unknown,
|
|
36
|
-
callback?: ExecCallback,
|
|
37
|
-
) => {
|
|
38
|
-
const key = `${command} ${args[0]}`;
|
|
39
|
-
const script = execScripts.get(key);
|
|
40
|
-
queueMicrotask(() => {
|
|
41
|
-
if (!callback) return;
|
|
42
|
-
if (!script) {
|
|
43
|
-
callback(new Error(`No script for ${key}`), "", "");
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
if (script.error) {
|
|
47
|
-
callback(script.error, "", "");
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
callback(null, script.stdout ?? "", "");
|
|
51
|
-
});
|
|
52
|
-
// Return value is not used by execFileWithTimeout.
|
|
53
|
-
return {} as ReturnType<typeof realChildProcess.execFile>;
|
|
54
|
-
},
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
mock.module("node:child_process", () => ({
|
|
58
|
-
...realChildProcess,
|
|
59
|
-
execFile: execFileMock,
|
|
60
|
-
}));
|
|
18
|
+
/** Fixed resolved `bun` path so install script keys are predictable. */
|
|
19
|
+
const BUN_BIN = "/usr/local/bin/bun";
|
|
20
|
+
const BUN_ADD_KEY = `${BUN_BIN} add`;
|
|
61
21
|
|
|
62
22
|
// Default ACP config used by these tests: the `unknown-agent` entry is here
|
|
63
|
-
// to give the "
|
|
23
|
+
// to give the "unmapped binary" tests a configured agent whose command isn't
|
|
64
24
|
// in DEFAULT_AGENT_NPM_PACKAGES.
|
|
65
25
|
const DEFAULT_TEST_AGENTS = {
|
|
66
26
|
claude: { command: "claude-agent-acp", args: [] },
|
|
@@ -187,8 +147,10 @@ mock.module("../../acp/index.js", () => ({
|
|
|
187
147
|
getAcpSessionManager: () => ({ spawn: spawnMock }),
|
|
188
148
|
}));
|
|
189
149
|
|
|
190
|
-
const { executeAcpSpawn
|
|
191
|
-
|
|
150
|
+
const { executeAcpSpawn } = await import("./spawn.js");
|
|
151
|
+
const { _resetAdapterInstallCacheForTests } = await import(
|
|
152
|
+
"../../acp/auto-install.js"
|
|
153
|
+
);
|
|
192
154
|
|
|
193
155
|
// ---------------------------------------------------------------------------
|
|
194
156
|
// Helpers
|
|
@@ -204,11 +166,12 @@ function makeContext(): ToolContext {
|
|
|
204
166
|
}
|
|
205
167
|
|
|
206
168
|
beforeEach(() => {
|
|
207
|
-
|
|
208
|
-
execFileMock.mockClear();
|
|
169
|
+
resetExecFileStub();
|
|
209
170
|
spawnMock.mockClear();
|
|
210
|
-
|
|
171
|
+
_resetAdapterInstallCacheForTests();
|
|
211
172
|
config.setConfig({ agents: DEFAULT_TEST_AGENTS });
|
|
173
|
+
// Default: every command (including bun and the adapters) on PATH, so
|
|
174
|
+
// spawns resolve directly with no install.
|
|
212
175
|
which.setWhich((cmd) => `/usr/local/bin/${cmd}`);
|
|
213
176
|
// Default: vault has a claude token so the preflight in `prepareAgentEnv`
|
|
214
177
|
// succeeds for tests that don't care about env injection. Env-injection
|
|
@@ -222,135 +185,41 @@ beforeEach(() => {
|
|
|
222
185
|
// Tests
|
|
223
186
|
// ---------------------------------------------------------------------------
|
|
224
187
|
|
|
225
|
-
describe("executeAcpSpawn
|
|
226
|
-
test("
|
|
227
|
-
execScripts.set("npm ls", { error: new Error("npm not installed") });
|
|
228
|
-
execScripts.set("npm view", { error: new Error("npm not installed") });
|
|
229
|
-
|
|
188
|
+
describe("executeAcpSpawn - happy path", () => {
|
|
189
|
+
test("binary on PATH: spawns directly with no install and no npm probe", async () => {
|
|
230
190
|
const result = await executeAcpSpawn(
|
|
231
191
|
{ agent: "claude", task: "do something" },
|
|
232
192
|
makeContext(),
|
|
233
193
|
);
|
|
234
194
|
|
|
235
195
|
expect(result.isError).toBe(false);
|
|
196
|
+
// No package manager is ever invoked when the binary is already present.
|
|
197
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
236
198
|
expect(result.content).not.toContain("outdated");
|
|
237
|
-
expect(result.content).not.toContain("Note:");
|
|
238
|
-
// Spawn was actually invoked.
|
|
239
199
|
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
240
|
-
|
|
241
|
-
const lines = result.content.split("\n\n");
|
|
242
|
-
const payload = JSON.parse(lines[0]);
|
|
200
|
+
const payload = JSON.parse(result.content);
|
|
243
201
|
expect(payload.acpSessionId).toBe("acp-session-test");
|
|
244
202
|
expect(payload.status).toBe("running");
|
|
203
|
+
// The real adapter binary is spawned (no `bun x` wrapper).
|
|
204
|
+
const agentConfigArg = spawnMock.mock.calls[0][1] as { command: string };
|
|
205
|
+
expect(agentConfigArg.command).toBe("claude-agent-acp");
|
|
245
206
|
});
|
|
246
207
|
|
|
247
|
-
test("
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
},
|
|
253
|
-
}),
|
|
254
|
-
});
|
|
255
|
-
execScripts.set("npm view", { stdout: "0.2.0\n" });
|
|
256
|
-
|
|
257
|
-
const result = await executeAcpSpawn(
|
|
258
|
-
{ agent: "claude", task: "do something" },
|
|
259
|
-
makeContext(),
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
expect(result.isError).toBe(false);
|
|
263
|
-
expect(result.content).toContain("outdated");
|
|
264
|
-
expect(result.content).toContain("@agentclientprotocol/claude-agent-acp");
|
|
265
|
-
expect(result.content).toContain("0.1.0");
|
|
266
|
-
expect(result.content).toContain("0.2.0");
|
|
267
|
-
expect(result.content).toContain(
|
|
268
|
-
"npm install -g @agentclientprotocol/claude-agent-acp@0.2.0",
|
|
269
|
-
);
|
|
270
|
-
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
271
|
-
// Payload still parses as JSON before the warning suffix.
|
|
272
|
-
const [payloadJson] = result.content.split("\n\n");
|
|
273
|
-
const payload = JSON.parse(payloadJson);
|
|
274
|
-
expect(payload.acpSessionId).toBe("acp-session-test");
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
test("up-to-date version: spawn proceeds, no warning", async () => {
|
|
278
|
-
execScripts.set("npm ls", {
|
|
279
|
-
stdout: JSON.stringify({
|
|
280
|
-
dependencies: {
|
|
281
|
-
"@agentclientprotocol/claude-agent-acp": { version: "0.2.0" },
|
|
282
|
-
},
|
|
283
|
-
}),
|
|
284
|
-
});
|
|
285
|
-
execScripts.set("npm view", { stdout: "0.2.0\n" });
|
|
286
|
-
|
|
287
|
-
const result = await executeAcpSpawn(
|
|
288
|
-
{ agent: "claude", task: "do something" },
|
|
289
|
-
makeContext(),
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
expect(result.isError).toBe(false);
|
|
293
|
-
expect(result.content).not.toContain("outdated");
|
|
294
|
-
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test("unknown command: no version check is performed", async () => {
|
|
298
|
-
// No execScripts set — if the implementation tried to run npm, the
|
|
299
|
-
// mock would callback with "No script for ..." and we could detect
|
|
300
|
-
// the failure. But since the registry doesn't include this command,
|
|
301
|
-
// the implementation should skip the check entirely without calling
|
|
302
|
-
// execFile.
|
|
303
|
-
execFileMock.mockClear();
|
|
208
|
+
test("default-profile fallback when user config is empty", async () => {
|
|
209
|
+
// No user `agents.codex` entry, but `agent: "codex"` works via the bundled
|
|
210
|
+
// default profile (command: "codex-acp"). The resolver merges defaults
|
|
211
|
+
// automatically.
|
|
212
|
+
config.setConfig({ agents: {} });
|
|
304
213
|
|
|
305
214
|
const result = await executeAcpSpawn(
|
|
306
|
-
{ agent: "
|
|
215
|
+
{ agent: "codex", task: "do something" },
|
|
307
216
|
makeContext(),
|
|
308
217
|
);
|
|
309
218
|
|
|
310
219
|
expect(result.isError).toBe(false);
|
|
311
|
-
expect(execFileMock).not.toHaveBeenCalled();
|
|
312
220
|
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
313
|
-
|
|
314
|
-
expect(
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
test("cached null result: second call does not reprobe", async () => {
|
|
318
|
-
execScripts.set("npm ls", { error: new Error("npm not installed") });
|
|
319
|
-
execScripts.set("npm view", { error: new Error("npm not installed") });
|
|
320
|
-
|
|
321
|
-
await executeAcpSpawn({ agent: "claude", task: "task 1" }, makeContext());
|
|
322
|
-
const firstCallCount = execFileMock.mock.calls.length;
|
|
323
|
-
expect(firstCallCount).toBeGreaterThan(0);
|
|
324
|
-
|
|
325
|
-
await executeAcpSpawn({ agent: "claude", task: "task 2" }, makeContext());
|
|
326
|
-
// No additional execFile calls — result was cached.
|
|
327
|
-
expect(execFileMock.mock.calls.length).toBe(firstCallCount);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test("cached outdated result: second call does not reprobe but still warns", async () => {
|
|
331
|
-
execScripts.set("npm ls", {
|
|
332
|
-
stdout: JSON.stringify({
|
|
333
|
-
dependencies: {
|
|
334
|
-
"@agentclientprotocol/claude-agent-acp": { version: "0.1.0" },
|
|
335
|
-
},
|
|
336
|
-
}),
|
|
337
|
-
});
|
|
338
|
-
execScripts.set("npm view", { stdout: "0.2.0\n" });
|
|
339
|
-
|
|
340
|
-
const first = await executeAcpSpawn(
|
|
341
|
-
{ agent: "claude", task: "task 1" },
|
|
342
|
-
makeContext(),
|
|
343
|
-
);
|
|
344
|
-
expect(first.content).toContain("outdated");
|
|
345
|
-
const firstCallCount = execFileMock.mock.calls.length;
|
|
346
|
-
|
|
347
|
-
const second = await executeAcpSpawn(
|
|
348
|
-
{ agent: "claude", task: "task 2" },
|
|
349
|
-
makeContext(),
|
|
350
|
-
);
|
|
351
|
-
expect(second.content).toContain("outdated");
|
|
352
|
-
// No additional execFile calls — outdated info was cached.
|
|
353
|
-
expect(execFileMock.mock.calls.length).toBe(firstCallCount);
|
|
221
|
+
const agentConfigArg = spawnMock.mock.calls[0][1] as { command: string };
|
|
222
|
+
expect(agentConfigArg.command).toBe("codex-acp");
|
|
354
223
|
});
|
|
355
224
|
});
|
|
356
225
|
|
|
@@ -391,8 +260,8 @@ describe("executeAcpSpawn — input validation", () => {
|
|
|
391
260
|
expect(result.content).toContain("acp.enabled");
|
|
392
261
|
});
|
|
393
262
|
|
|
394
|
-
test("missing binary returns install hint", async () => {
|
|
395
|
-
which.setWhich({});
|
|
263
|
+
test("missing binary + bun absent returns install hint, no install attempted", async () => {
|
|
264
|
+
which.setWhich({}); // neither bun nor the adapter on PATH
|
|
396
265
|
const result = await executeAcpSpawn(
|
|
397
266
|
{ agent: "claude", task: "do something" },
|
|
398
267
|
makeContext(),
|
|
@@ -400,65 +269,181 @@ describe("executeAcpSpawn — input validation", () => {
|
|
|
400
269
|
expect(result.isError).toBe(true);
|
|
401
270
|
expect(result.content).toContain("claude-agent-acp is not on PATH");
|
|
402
271
|
expect(result.content).toContain(
|
|
403
|
-
"
|
|
272
|
+
"bun add -g @agentclientprotocol/claude-agent-acp",
|
|
404
273
|
);
|
|
274
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
405
275
|
expect(spawnMock).not.toHaveBeenCalled();
|
|
406
276
|
});
|
|
277
|
+
});
|
|
407
278
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
279
|
+
describe("executeAcpSpawn: sandboxed bun auto-install on missing binary", () => {
|
|
280
|
+
test("known command + bun present: installs via bun and spawn proceeds with a note", async () => {
|
|
281
|
+
// Only bun is on PATH until `bun add --global` runs, simulating a
|
|
282
|
+
// successful global install that links the adapter bin onto PATH.
|
|
283
|
+
let binaryOnPath = false;
|
|
284
|
+
which.setWhich((cmd) => {
|
|
285
|
+
if (cmd === "bun") return BUN_BIN;
|
|
286
|
+
if (binaryOnPath) return `/usr/local/bin/${cmd}`;
|
|
287
|
+
return null;
|
|
288
|
+
});
|
|
289
|
+
execScripts.set(BUN_ADD_KEY, {
|
|
290
|
+
stdout: "",
|
|
291
|
+
onCall: () => {
|
|
292
|
+
binaryOnPath = true;
|
|
293
|
+
},
|
|
294
|
+
});
|
|
415
295
|
|
|
416
296
|
const result = await executeAcpSpawn(
|
|
417
|
-
{ agent: "
|
|
297
|
+
{ agent: "claude", task: "do something" },
|
|
418
298
|
makeContext(),
|
|
419
299
|
);
|
|
420
300
|
|
|
421
301
|
expect(result.isError).toBe(false);
|
|
422
302
|
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
303
|
+
const payload = JSON.parse(result.content);
|
|
304
|
+
expect(payload.message).toContain(
|
|
305
|
+
"Installed @agentclientprotocol/claude-agent-acp automatically.",
|
|
306
|
+
);
|
|
307
|
+
// The real binary was spawned with cwd = the project dir and token
|
|
308
|
+
// injected (trusted-binary config, no resolution at spawn).
|
|
309
|
+
const agentConfigArg = spawnMock.mock.calls[0][1] as {
|
|
310
|
+
command: string;
|
|
311
|
+
env?: Record<string, string>;
|
|
312
|
+
};
|
|
313
|
+
expect(agentConfigArg.command).toBe("claude-agent-acp");
|
|
314
|
+
expect(agentConfigArg.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe(
|
|
315
|
+
"default-test-token",
|
|
316
|
+
);
|
|
317
|
+
expect(spawnMock.mock.calls[0][3]).toBe("/tmp");
|
|
318
|
+
|
|
319
|
+
// Exactly one install, and it was `bun add --global` (never npm).
|
|
320
|
+
expect(execFileMock).toHaveBeenCalledTimes(1);
|
|
321
|
+
const [command, args] = execFileMock.mock.calls[0];
|
|
322
|
+
expect(command).toBe(BUN_BIN);
|
|
323
|
+
expect(args).toEqual([
|
|
324
|
+
"add",
|
|
325
|
+
"--global",
|
|
326
|
+
"@agentclientprotocol/claude-agent-acp",
|
|
327
|
+
]);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("the installer cwd is a temp dir (not the project cwd) with secrets stripped", async () => {
|
|
331
|
+
let binaryOnPath = false;
|
|
332
|
+
which.setWhich((cmd) => {
|
|
333
|
+
if (cmd === "bun") return BUN_BIN;
|
|
334
|
+
if (binaryOnPath) return `/usr/local/bin/${cmd}`;
|
|
335
|
+
return null;
|
|
336
|
+
});
|
|
337
|
+
execScripts.set(BUN_ADD_KEY, {
|
|
338
|
+
stdout: "",
|
|
339
|
+
onCall: () => {
|
|
340
|
+
binaryOnPath = true;
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
await executeAcpSpawn(
|
|
345
|
+
{ agent: "claude", task: "do something", cwd: "/untrusted/project" },
|
|
346
|
+
makeContext(),
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const options = execFileMock.mock.calls[0][2] as {
|
|
350
|
+
cwd?: string;
|
|
351
|
+
env?: NodeJS.ProcessEnv;
|
|
352
|
+
};
|
|
353
|
+
expect(options.cwd).toBeDefined();
|
|
354
|
+
expect(options.cwd).not.toBe("/untrusted/project");
|
|
355
|
+
expect(options.cwd).toContain("vellum-acp-install-");
|
|
356
|
+
expect(options.env?.CLAUDE_CODE_OAUTH_TOKEN).toBeUndefined();
|
|
357
|
+
expect(options.env?.GEMINI_API_KEY).toBeUndefined();
|
|
358
|
+
expect(options.env?.BUN_CONFIG_REGISTRY).toBe(
|
|
359
|
+
"https://registry.npmjs.org/",
|
|
360
|
+
);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("unmapped command: no install attempted, plain hint returned", async () => {
|
|
364
|
+
which.setWhich({});
|
|
365
|
+
|
|
366
|
+
const result = await executeAcpSpawn(
|
|
367
|
+
{ agent: "unknown-agent", task: "do something" },
|
|
368
|
+
makeContext(),
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
expect(result.isError).toBe(true);
|
|
372
|
+
expect(result.content).toContain("some-other-binary is not on PATH");
|
|
373
|
+
expect(result.content).toContain("Install 'some-other-binary'");
|
|
374
|
+
expect(result.content).not.toContain("auto-install failed");
|
|
375
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
376
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("no client connected: no install attempted even when the binary is missing", async () => {
|
|
380
|
+
// The no-client guard is a pure precondition and must run BEFORE the
|
|
381
|
+
// auto-install side effect: without a client the spawn fails anyway, so
|
|
382
|
+
// the host must not be mutated by a global install (which can also block
|
|
383
|
+
// for up to the install timeout).
|
|
384
|
+
which.setWhich({ bun: BUN_BIN });
|
|
385
|
+
|
|
386
|
+
const result = await executeAcpSpawn(
|
|
387
|
+
{ agent: "claude", task: "do something" },
|
|
388
|
+
{ ...makeContext(), sendToClient: undefined },
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
expect(result.isError).toBe(true);
|
|
392
|
+
expect(result.content).toContain("No client connected");
|
|
393
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
394
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("install failure: hint and install failure both surface, never npm", async () => {
|
|
398
|
+
which.setWhich({ bun: BUN_BIN }); // bun present, adapter never appears
|
|
399
|
+
execScripts.set(BUN_ADD_KEY, {
|
|
400
|
+
error: new Error("EACCES: permission denied"),
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const result = await executeAcpSpawn(
|
|
404
|
+
{ agent: "claude", task: "do something" },
|
|
405
|
+
makeContext(),
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
expect(result.isError).toBe(true);
|
|
409
|
+
expect(result.content).toContain("claude-agent-acp is not on PATH");
|
|
410
|
+
expect(result.content).toContain(
|
|
411
|
+
"bun add -g @agentclientprotocol/claude-agent-acp",
|
|
412
|
+
);
|
|
413
|
+
expect(result.content).toContain("auto-install failed");
|
|
414
|
+
expect(result.content).toContain("EACCES");
|
|
415
|
+
for (const call of execFileMock.mock.calls) {
|
|
416
|
+
expect(call[0]).not.toBe("npm");
|
|
417
|
+
}
|
|
418
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
426
419
|
});
|
|
427
420
|
});
|
|
428
421
|
|
|
429
422
|
describe("executeAcpSpawn — per-agent resume hint", () => {
|
|
430
423
|
test("claude payload includes the `claude --resume` hint", async () => {
|
|
431
|
-
execScripts.set("npm ls", { error: new Error("npm not installed") });
|
|
432
|
-
execScripts.set("npm view", { error: new Error("npm not installed") });
|
|
433
|
-
|
|
434
424
|
const result = await executeAcpSpawn(
|
|
435
425
|
{ agent: "claude", task: "do something" },
|
|
436
426
|
makeContext(),
|
|
437
427
|
);
|
|
438
428
|
|
|
439
429
|
expect(result.isError).toBe(false);
|
|
440
|
-
const
|
|
441
|
-
const payload = JSON.parse(payloadJson);
|
|
430
|
+
const payload = JSON.parse(result.content);
|
|
442
431
|
expect(payload.message).toContain("claude --resume");
|
|
443
|
-
expect(payload.message).toContain("To resume
|
|
432
|
+
expect(payload.message).toContain("To resume:");
|
|
444
433
|
});
|
|
445
434
|
|
|
446
435
|
test("non-claude payload omits the `claude --resume` hint", async () => {
|
|
447
436
|
// `claude --resume <id>` is Claude Code-specific. Codex (and any other
|
|
448
437
|
// adapter) should not have that command suggested back to the user.
|
|
449
|
-
execScripts.set("npm ls", { error: new Error("npm not installed") });
|
|
450
|
-
execScripts.set("npm view", { error: new Error("npm not installed") });
|
|
451
|
-
|
|
452
438
|
const result = await executeAcpSpawn(
|
|
453
439
|
{ agent: "codex", task: "do something" },
|
|
454
440
|
makeContext(),
|
|
455
441
|
);
|
|
456
442
|
|
|
457
443
|
expect(result.isError).toBe(false);
|
|
458
|
-
const
|
|
459
|
-
const payload = JSON.parse(payloadJson);
|
|
444
|
+
const payload = JSON.parse(result.content);
|
|
460
445
|
expect(payload.message).not.toContain("claude --resume");
|
|
461
|
-
expect(payload.message).not.toContain("To resume
|
|
446
|
+
expect(payload.message).not.toContain("To resume:");
|
|
462
447
|
});
|
|
463
448
|
});
|
|
464
449
|
|
|
@@ -482,8 +467,6 @@ describe("executeAcpSpawn — CLAUDE_CODE_OAUTH_TOKEN injection", () => {
|
|
|
482
467
|
vaultStore.clear();
|
|
483
468
|
metadataStore.clear();
|
|
484
469
|
vaultStore.set("acp/claude_oauth_token", "tool-vault-token-abc");
|
|
485
|
-
execScripts.set("npm ls", { error: new Error("npm not installed") });
|
|
486
|
-
execScripts.set("npm view", { error: new Error("npm not installed") });
|
|
487
470
|
|
|
488
471
|
const result = await executeAcpSpawn(
|
|
489
472
|
{ agent: "claude", task: "do something" },
|
|
@@ -514,8 +497,6 @@ describe("executeAcpSpawn — CLAUDE_CODE_OAUTH_TOKEN injection", () => {
|
|
|
514
497
|
"unknown-agent": { command: "some-other-binary", args: [] },
|
|
515
498
|
},
|
|
516
499
|
});
|
|
517
|
-
execScripts.set("npm ls", { error: new Error("npm not installed") });
|
|
518
|
-
execScripts.set("npm view", { error: new Error("npm not installed") });
|
|
519
500
|
|
|
520
501
|
const result = await executeAcpSpawn(
|
|
521
502
|
{ agent: "claude", task: "do something" },
|