@vellumai/assistant 0.8.8 → 0.8.9-staging.1
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/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 +236 -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 +185 -140
- 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-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -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__/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 +59 -103
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +94 -189
- package/src/__tests__/conversation-agent-loop.test.ts +249 -490
- 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-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 +18 -16
- 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 +73 -6
- 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__/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 +101 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +196 -0
- package/src/acp/auto-install.ts +177 -0
- package/src/acp/client-handler.ts +31 -0
- package/src/acp/feature-gate.test.ts +48 -0
- package/src/acp/feature-gate.ts +34 -0
- package/src/acp/prepare-agent-env.ts +83 -29
- package/src/acp/resolve-agent.test.ts +320 -7
- package/src/acp/resolve-agent.ts +182 -18
- package/src/acp/resume-hint.ts +25 -0
- package/src/acp/session-manager.ts +495 -73
- package/src/acp/types.ts +8 -0
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +390 -240
- 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 +83 -31
- 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 +29 -0
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/feature-flag-registry.json +14 -5
- package/src/config/schemas/heartbeat.ts +9 -0
- 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 +167 -1005
- 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 +383 -56
- 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 -0
- 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/plugins/defaults/compaction/manager-store.ts +44 -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 +103 -38
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +167 -56
- 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/proactive-artifact/aux-message-injector.ts +1 -1
- package/src/proactive-artifact/job.test.ts +1 -1
- 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 +283 -55
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +398 -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 +336 -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 +7 -1
- package/src/tools/acp/spawn.test.ts +158 -55
- package/src/tools/acp/spawn.ts +47 -72
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +13 -8
- package/src/tools/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/{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");
|
|
@@ -108,6 +109,11 @@ describe("executeAcpListAgents", () => {
|
|
|
108
109
|
expect(codex.unavailableReason).toBe("'codex-acp' is not on PATH");
|
|
109
110
|
expect(codex.setupHint).toBe("npm i -g @zed-industries/codex-acp");
|
|
110
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("npm i -g @google/gemini-cli");
|
|
116
|
+
|
|
111
117
|
const claude = parsed.agents.find((a: { id: string }) => a.id === "claude");
|
|
112
118
|
expect(claude.available).toBe(true);
|
|
113
119
|
expect(claude.setupHint).toBeUndefined();
|
|
@@ -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,55 +9,11 @@ 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
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Per-call scripted responses for `execFile`. Keyed by `${command} ${args[0]}`
|
|
27
|
-
* so tests can target `npm ls` and `npm view` independently.
|
|
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
|
-
}));
|
|
12
|
+
const {
|
|
13
|
+
execScripts,
|
|
14
|
+
execFileMock,
|
|
15
|
+
reset: resetExecFileStub,
|
|
16
|
+
} = installExecFileStub();
|
|
61
17
|
|
|
62
18
|
// Default ACP config used by these tests: the `unknown-agent` entry is here
|
|
63
19
|
// to give the "no version check" test a configured agent whose binary isn't
|
|
@@ -189,6 +145,9 @@ mock.module("../../acp/index.js", () => ({
|
|
|
189
145
|
|
|
190
146
|
const { executeAcpSpawn, _resetAdapterVersionCacheForTests } =
|
|
191
147
|
await import("./spawn.js");
|
|
148
|
+
const { _resetAdapterInstallCacheForTests } = await import(
|
|
149
|
+
"../../acp/auto-install.js"
|
|
150
|
+
);
|
|
192
151
|
|
|
193
152
|
// ---------------------------------------------------------------------------
|
|
194
153
|
// Helpers
|
|
@@ -204,10 +163,10 @@ function makeContext(): ToolContext {
|
|
|
204
163
|
}
|
|
205
164
|
|
|
206
165
|
beforeEach(() => {
|
|
207
|
-
|
|
208
|
-
execFileMock.mockClear();
|
|
166
|
+
resetExecFileStub();
|
|
209
167
|
spawnMock.mockClear();
|
|
210
168
|
_resetAdapterVersionCacheForTests();
|
|
169
|
+
_resetAdapterInstallCacheForTests();
|
|
211
170
|
config.setConfig({ agents: DEFAULT_TEST_AGENTS });
|
|
212
171
|
which.setWhich((cmd) => `/usr/local/bin/${cmd}`);
|
|
213
172
|
// Default: vault has a claude token so the preflight in `prepareAgentEnv`
|
|
@@ -391,8 +350,9 @@ describe("executeAcpSpawn — input validation", () => {
|
|
|
391
350
|
expect(result.content).toContain("acp.enabled");
|
|
392
351
|
});
|
|
393
352
|
|
|
394
|
-
test("missing binary returns install hint", async () => {
|
|
353
|
+
test("missing binary returns install hint when auto-install fails", async () => {
|
|
395
354
|
which.setWhich({});
|
|
355
|
+
execScripts.set("npm i", { error: new Error("npm not installed") });
|
|
396
356
|
const result = await executeAcpSpawn(
|
|
397
357
|
{ agent: "claude", task: "do something" },
|
|
398
358
|
makeContext(),
|
|
@@ -426,6 +386,149 @@ describe("executeAcpSpawn — input validation", () => {
|
|
|
426
386
|
});
|
|
427
387
|
});
|
|
428
388
|
|
|
389
|
+
describe("executeAcpSpawn: auto-install on missing binary", () => {
|
|
390
|
+
test("known command: installs the mapped package and spawn proceeds with a note", async () => {
|
|
391
|
+
// Binary appears on PATH only after `npm i -g` runs, simulating a
|
|
392
|
+
// successful global install.
|
|
393
|
+
let binaryOnPath = false;
|
|
394
|
+
which.setWhich((cmd) => (binaryOnPath ? `/usr/local/bin/${cmd}` : null));
|
|
395
|
+
execScripts.set("npm i", {
|
|
396
|
+
stdout: "",
|
|
397
|
+
onCall: () => {
|
|
398
|
+
binaryOnPath = true;
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
// Version probes after the install: best-effort, scripted to skip.
|
|
402
|
+
execScripts.set("npm ls", { error: new Error("skip") });
|
|
403
|
+
execScripts.set("npm view", { error: new Error("skip") });
|
|
404
|
+
|
|
405
|
+
const result = await executeAcpSpawn(
|
|
406
|
+
{ agent: "claude", task: "do something" },
|
|
407
|
+
makeContext(),
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
expect(result.isError).toBe(false);
|
|
411
|
+
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
412
|
+
const [payloadJson] = result.content.split("\n\n");
|
|
413
|
+
const payload = JSON.parse(payloadJson);
|
|
414
|
+
expect(payload.message).toContain(
|
|
415
|
+
"Installed @agentclientprotocol/claude-agent-acp automatically.",
|
|
416
|
+
);
|
|
417
|
+
const installCalls = execFileMock.mock.calls.filter(
|
|
418
|
+
(call) => (call[1] as string[])[0] === "i",
|
|
419
|
+
);
|
|
420
|
+
expect(installCalls).toHaveLength(1);
|
|
421
|
+
expect(installCalls[0][1]).toEqual([
|
|
422
|
+
"i",
|
|
423
|
+
"-g",
|
|
424
|
+
"@agentclientprotocol/claude-agent-acp",
|
|
425
|
+
]);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("unknown command: no install attempted, plain hint returned", async () => {
|
|
429
|
+
which.setWhich({});
|
|
430
|
+
|
|
431
|
+
const result = await executeAcpSpawn(
|
|
432
|
+
{ agent: "unknown-agent", task: "do something" },
|
|
433
|
+
makeContext(),
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
expect(result.isError).toBe(true);
|
|
437
|
+
expect(result.content).toContain("some-other-binary is not on PATH");
|
|
438
|
+
expect(result.content).toContain("Install 'some-other-binary'");
|
|
439
|
+
expect(result.content).not.toContain("auto-install failed");
|
|
440
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
441
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test("no client connected: no install attempted even when the binary is missing", async () => {
|
|
445
|
+
// The no-client guard is a pure precondition and must run BEFORE the
|
|
446
|
+
// auto-install side effect: without a client the spawn fails anyway, so
|
|
447
|
+
// the host must not be mutated by `npm i -g` (which can also block for
|
|
448
|
+
// up to the install timeout).
|
|
449
|
+
which.setWhich({});
|
|
450
|
+
|
|
451
|
+
const result = await executeAcpSpawn(
|
|
452
|
+
{ agent: "claude", task: "do something" },
|
|
453
|
+
{ ...makeContext(), sendToClient: undefined },
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
expect(result.isError).toBe(true);
|
|
457
|
+
expect(result.content).toContain("No client connected");
|
|
458
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
459
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test("npm failure: hint and install failure both surface", async () => {
|
|
463
|
+
which.setWhich({});
|
|
464
|
+
execScripts.set("npm i", {
|
|
465
|
+
error: new Error("EACCES: permission denied"),
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const result = await executeAcpSpawn(
|
|
469
|
+
{ agent: "claude", task: "do something" },
|
|
470
|
+
makeContext(),
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
expect(result.isError).toBe(true);
|
|
474
|
+
expect(result.content).toContain("claude-agent-acp is not on PATH");
|
|
475
|
+
expect(result.content).toContain(
|
|
476
|
+
"npm i -g @agentclientprotocol/claude-agent-acp",
|
|
477
|
+
);
|
|
478
|
+
expect(result.content).toContain("auto-install failed");
|
|
479
|
+
expect(result.content).toContain("EACCES");
|
|
480
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
describe("executeAcpSpawn - bunx fallback when binary missing", () => {
|
|
485
|
+
test("binary missing + bun present: spawns via `bun x` with env injection and resume hint, no npm calls", async () => {
|
|
486
|
+
// Only bun is on PATH - the platform-hosted image (bun, no node/npm).
|
|
487
|
+
which.setWhich({ bun: "/usr/local/bin/bun" });
|
|
488
|
+
|
|
489
|
+
const result = await executeAcpSpawn(
|
|
490
|
+
{ agent: "claude", task: "do something" },
|
|
491
|
+
makeContext(),
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
expect(result.isError).toBe(false);
|
|
495
|
+
// No npm install AND no npm version probe: bunx fetches the package on
|
|
496
|
+
// first use and there is no global install to compare.
|
|
497
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
498
|
+
expect(result.content).not.toContain("outdated");
|
|
499
|
+
|
|
500
|
+
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
501
|
+
const agentConfigArg = spawnMock.mock.calls[0][1] as {
|
|
502
|
+
command: string;
|
|
503
|
+
args: string[];
|
|
504
|
+
adapterCommand?: string;
|
|
505
|
+
env?: Record<string, string>;
|
|
506
|
+
};
|
|
507
|
+
expect(agentConfigArg.command).toBe("bun");
|
|
508
|
+
expect(agentConfigArg.args).toEqual([
|
|
509
|
+
"x",
|
|
510
|
+
"--bun",
|
|
511
|
+
"@agentclientprotocol/claude-agent-acp",
|
|
512
|
+
]);
|
|
513
|
+
expect(agentConfigArg.adapterCommand).toBe("claude-agent-acp");
|
|
514
|
+
// CLAUDE_CODE_OAUTH_TOKEN injection still applies to the bunx-resolved
|
|
515
|
+
// claude adapter (gated on adapterCommand, not the spawn command).
|
|
516
|
+
expect(agentConfigArg.env?.CLAUDE_CODE_OAUTH_TOKEN).toBe(
|
|
517
|
+
"default-test-token",
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
const [payloadJson] = result.content.split("\n\n");
|
|
521
|
+
const payload = JSON.parse(payloadJson);
|
|
522
|
+
// No auto-install happened; the claude resume hint still fires.
|
|
523
|
+
expect(payload.message).not.toContain("Installed");
|
|
524
|
+
expect(payload.message).toContain("claude --resume");
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// The bun-absent npm fallback is covered by the existing auto-install
|
|
528
|
+
// suite below ("known command: installs the mapped package..."), whose
|
|
529
|
+
// which-stub leaves bun off PATH until the install completes.
|
|
530
|
+
});
|
|
531
|
+
|
|
429
532
|
describe("executeAcpSpawn — per-agent resume hint", () => {
|
|
430
533
|
test("claude payload includes the `claude --resume` hint", async () => {
|
|
431
534
|
execScripts.set("npm ls", { error: new Error("npm not installed") });
|
|
@@ -440,7 +543,7 @@ describe("executeAcpSpawn — per-agent resume hint", () => {
|
|
|
440
543
|
const [payloadJson] = result.content.split("\n\n");
|
|
441
544
|
const payload = JSON.parse(payloadJson);
|
|
442
545
|
expect(payload.message).toContain("claude --resume");
|
|
443
|
-
expect(payload.message).toContain("To resume
|
|
546
|
+
expect(payload.message).toContain("To resume:");
|
|
444
547
|
});
|
|
445
548
|
|
|
446
549
|
test("non-claude payload omits the `claude --resume` hint", async () => {
|
|
@@ -458,7 +561,7 @@ describe("executeAcpSpawn — per-agent resume hint", () => {
|
|
|
458
561
|
const [payloadJson] = result.content.split("\n\n");
|
|
459
562
|
const payload = JSON.parse(payloadJson);
|
|
460
563
|
expect(payload.message).not.toContain("claude --resume");
|
|
461
|
-
expect(payload.message).not.toContain("To resume
|
|
564
|
+
expect(payload.message).not.toContain("To resume:");
|
|
462
565
|
});
|
|
463
566
|
});
|
|
464
567
|
|
package/src/tools/acp/spawn.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
execFileWithTimeout,
|
|
3
|
+
resolveAgentWithAutoInstall,
|
|
4
|
+
} from "../../acp/auto-install.js";
|
|
3
5
|
import { getAcpSessionManager } from "../../acp/index.js";
|
|
4
6
|
import { prepareAgentEnv } from "../../acp/prepare-agent-env.js";
|
|
5
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
adapterCommandOf,
|
|
9
|
+
formatResolveFailure,
|
|
10
|
+
runsViaBunx,
|
|
11
|
+
} from "../../acp/resolve-agent.js";
|
|
12
|
+
import { claudeResumeHint } from "../../acp/resume-hint.js";
|
|
6
13
|
import { DEFAULT_AGENT_NPM_PACKAGES } from "../../config/acp-defaults.js";
|
|
7
14
|
import { getLogger } from "../../util/logger.js";
|
|
8
15
|
import type { ToolContext, ToolExecutionResult } from "../types.js";
|
|
16
|
+
import { getSendToClient } from "./context.js";
|
|
9
17
|
|
|
10
18
|
const log = getLogger("acp:spawn");
|
|
11
19
|
|
|
@@ -25,35 +33,6 @@ interface AdapterVersionInfo {
|
|
|
25
33
|
packageName: string;
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
/**
|
|
29
|
-
* Run `execFile` with an AbortController-driven timeout. Returns the stdout
|
|
30
|
-
* on success; throws on error or timeout. Caller treats any throw as a
|
|
31
|
-
* best-effort skip.
|
|
32
|
-
*/
|
|
33
|
-
function execFileWithTimeout(
|
|
34
|
-
command: string,
|
|
35
|
-
args: string[],
|
|
36
|
-
timeoutMs: number,
|
|
37
|
-
): Promise<string> {
|
|
38
|
-
return new Promise((resolve, reject) => {
|
|
39
|
-
const controller = new AbortController();
|
|
40
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
41
|
-
execFile(
|
|
42
|
-
command,
|
|
43
|
-
args,
|
|
44
|
-
{ signal: controller.signal, encoding: "utf8" },
|
|
45
|
-
(err, stdout) => {
|
|
46
|
-
clearTimeout(timer);
|
|
47
|
-
if (err) {
|
|
48
|
-
reject(err);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
resolve(stdout);
|
|
52
|
-
},
|
|
53
|
-
);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
36
|
/**
|
|
58
37
|
* Checks if the globally-installed ACP adapter for `command` is outdated.
|
|
59
38
|
* Best-effort: any error or timeout returns `null` (skipped). Unknown
|
|
@@ -135,34 +114,10 @@ export async function executeAcpSpawn(
|
|
|
135
114
|
return { content: '"task" is required.', isError: true };
|
|
136
115
|
}
|
|
137
116
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return { content: resolved.hint, isError: true };
|
|
143
|
-
case "unknown_agent":
|
|
144
|
-
return {
|
|
145
|
-
content: `Unknown agent "${agent}". Available: ${resolved.available.join(
|
|
146
|
-
", ",
|
|
147
|
-
)}.`,
|
|
148
|
-
isError: true,
|
|
149
|
-
};
|
|
150
|
-
case "binary_not_found":
|
|
151
|
-
return {
|
|
152
|
-
content: `${resolved.command} is not on PATH. ${resolved.hint}`,
|
|
153
|
-
isError: true,
|
|
154
|
-
};
|
|
155
|
-
default: {
|
|
156
|
-
const _exhaustive: never = resolved;
|
|
157
|
-
throw new Error(
|
|
158
|
-
`Unexpected acp resolver reason: ${(_exhaustive as { reason: string }).reason}`,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
const sendToClient = context.sendToClient as
|
|
164
|
-
| ((msg: { type: string; [key: string]: unknown }) => void)
|
|
165
|
-
| undefined;
|
|
117
|
+
// Pure precondition: check for a connected client BEFORE any side effects
|
|
118
|
+
// (auto-install mutates the host via `npm i -g` and can block for up to
|
|
119
|
+
// the install timeout). Without a client the spawn cannot succeed anyway.
|
|
120
|
+
const sendToClient = getSendToClient(context);
|
|
166
121
|
if (!sendToClient) {
|
|
167
122
|
return {
|
|
168
123
|
content: "No client connected - cannot spawn ACP agent.",
|
|
@@ -170,6 +125,17 @@ export async function executeAcpSpawn(
|
|
|
170
125
|
};
|
|
171
126
|
}
|
|
172
127
|
|
|
128
|
+
// Resolve the agent, silently auto-installing a missing allowlisted
|
|
129
|
+
// adapter binary (see acp/auto-install.ts). Shared with the HTTP route.
|
|
130
|
+
const { resolved, autoInstalledPackage, failureMessage } =
|
|
131
|
+
await resolveAgentWithAutoInstall(agent);
|
|
132
|
+
if (failureMessage) {
|
|
133
|
+
return { content: failureMessage, isError: true };
|
|
134
|
+
}
|
|
135
|
+
if (!resolved.ok) {
|
|
136
|
+
return { content: formatResolveFailure(agent, resolved), isError: true };
|
|
137
|
+
}
|
|
138
|
+
|
|
173
139
|
// Inject required env vars and preflight via the shared helper. Mirrors
|
|
174
140
|
// the HTTP route at `runtime/routes/acp-routes.ts:spawnSession` — both
|
|
175
141
|
// call sites MUST go through `prepareAgentEnv` before `manager.spawn`,
|
|
@@ -185,8 +151,12 @@ export async function executeAcpSpawn(
|
|
|
185
151
|
}
|
|
186
152
|
|
|
187
153
|
// Best-effort version check — never blocks the spawn. If outdated, we
|
|
188
|
-
// append a non-blocking warning to the success payload.
|
|
189
|
-
|
|
154
|
+
// append a non-blocking warning to the success payload. Skipped for
|
|
155
|
+
// bunx-resolved agents: there is no global npm install to compare (bun
|
|
156
|
+
// fetches the package on first use), and npm may not exist on the host.
|
|
157
|
+
const versionInfo = runsViaBunx(agentConfig)
|
|
158
|
+
? null
|
|
159
|
+
: await checkAdapterVersion(adapterCommandOf(agentConfig));
|
|
190
160
|
|
|
191
161
|
try {
|
|
192
162
|
const manager = getAcpSessionManager();
|
|
@@ -197,17 +167,21 @@ export async function executeAcpSpawn(
|
|
|
197
167
|
task,
|
|
198
168
|
cwd,
|
|
199
169
|
context.conversationId,
|
|
200
|
-
sendToClient
|
|
170
|
+
sendToClient,
|
|
201
171
|
);
|
|
202
172
|
|
|
203
|
-
//
|
|
204
|
-
// adapter
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
173
|
+
// Claude Code-only resume hint; empty for other adapters. Keyed off the
|
|
174
|
+
// canonical adapter command so it survives the bunx rewrite. See
|
|
175
|
+
// acp/resume-hint.ts for the gating rationale.
|
|
176
|
+
const hint = claudeResumeHint(
|
|
177
|
+
adapterCommandOf(agentConfig),
|
|
178
|
+
cwd,
|
|
179
|
+
protocolSessionId,
|
|
180
|
+
);
|
|
181
|
+
const resumeHint = hint ? ` ${hint}` : "";
|
|
182
|
+
const installNote = autoInstalledPackage
|
|
183
|
+
? ` Installed ${autoInstalledPackage} automatically.`
|
|
184
|
+
: "";
|
|
211
185
|
const payload = JSON.stringify({
|
|
212
186
|
acpSessionId,
|
|
213
187
|
protocolSessionId,
|
|
@@ -216,7 +190,8 @@ export async function executeAcpSpawn(
|
|
|
216
190
|
status: "running",
|
|
217
191
|
message:
|
|
218
192
|
`ACP agent "${agent}" spawned (session: ${protocolSessionId}). ` +
|
|
219
|
-
`Results stream back via SSE. You will be notified when it completes
|
|
193
|
+
`Results stream back via SSE. You will be notified when it completes.` +
|
|
194
|
+
`${installNote}${resumeHint}`,
|
|
220
195
|
});
|
|
221
196
|
|
|
222
197
|
let content = payload;
|