@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae
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/docs/activation-funnel-telemetry.md +310 -0
- 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 +263 -121
- package/package.json +2 -2
- package/src/__tests__/activation-early-marking.test.ts +120 -0
- 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 +23 -8
- 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 +6 -5
- 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 +146 -0
- package/src/__tests__/compaction-events.test.ts +7 -7
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
- package/src/__tests__/context-overflow-reducer.test.ts +265 -125
- package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
- package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
- 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 +158 -234
- package/src/__tests__/conversation-agent-loop.test.ts +342 -623
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
- package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-history-web-search.test.ts +6 -1
- package/src/__tests__/conversation-media-retry.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
- package/src/__tests__/conversation-process-callsite.test.ts +15 -15
- package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
- package/src/__tests__/conversation-queue.test.ts +10 -10
- package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
- package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
- package/src/__tests__/conversation-slash-queue.test.ts +7 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
- package/src/__tests__/conversation-speed-override.test.ts +11 -10
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
- package/src/__tests__/conversation-undo.test.ts +2 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
- package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +36 -0
- 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 +17 -2
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -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 +5 -1
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/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__/llm-resolver.test.ts +73 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
- package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
- package/src/__tests__/persist-unsendable-image.test.ts +97 -1
- package/src/__tests__/plugin-api-shim.test.ts +6 -9
- package/src/__tests__/plugin-bootstrap.test.ts +91 -81
- package/src/__tests__/plugin-registry.test.ts +3 -49
- package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -70
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
- package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
- package/src/__tests__/published-app-updater.test.ts +138 -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 +31 -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 +5 -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/__tests__/web-fetch.test.ts +45 -0
- package/src/acp/__tests__/agent-process.test.ts +161 -0
- package/src/acp/__tests__/client-handler.test.ts +40 -0
- package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
- 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/prepare-agent-env.ts +80 -27
- package/src/acp/resolve-agent.test.ts +188 -28
- package/src/acp/resolve-agent.ts +119 -42
- 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 +403 -251
- package/src/api/responses/conversation-message.ts +22 -2
- package/src/api/responses/memory-v3-selection-log.ts +19 -10
- package/src/approvals/guardian-request-resolvers.ts +27 -1
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
- package/src/cli/commands/memory-v3.ts +57 -199
- package/src/cli/commands/plugins.ts +43 -37
- package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
- package/src/cli/lib/install-from-github.ts +797 -60
- package/src/cli/lib/plugin-catalog-cache.ts +84 -0
- package/src/cli/lib/plugin-details.ts +416 -0
- package/src/cli/lib/plugin-marketplace.ts +215 -0
- package/src/cli/lib/search-plugins.ts +201 -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 +9 -3
- package/src/config/acp-schema.ts +1 -7
- package/src/config/bundled-skills/acp/SKILL.md +80 -41
- 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 -3
- package/src/config/feature-flag-registry.json +10 -16
- package/src/config/llm-resolver.ts +39 -7
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
- package/src/config/schemas/call-site-catalog.ts +0 -21
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +17 -3
- package/src/config/schemas/memory-v3.ts +58 -8
- package/src/config/seed-inference-profiles.ts +18 -0
- package/src/context/compactor.ts +22 -4
- package/src/context/post-turn-tool-result-truncation.ts +39 -1
- package/src/context/strip-injections.ts +8 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
- package/src/daemon/conversation-agent-loop.ts +308 -1297
- package/src/daemon/conversation-error.ts +31 -4
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +11 -255
- package/src/daemon/conversation-media-retry.ts +19 -6
- package/src/daemon/conversation-messaging.ts +17 -0
- package/src/daemon/conversation-process.ts +22 -141
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +361 -393
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +165 -11
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +444 -62
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/external-plugins-bootstrap.ts +36 -46
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-proxy-preactivation.ts +1 -3
- package/src/daemon/lifecycle.ts +21 -7
- package/src/daemon/persist-unsendable-image.ts +62 -25
- package/src/daemon/process-message.ts +1 -1
- package/src/daemon/server.ts +2 -0
- package/src/daemon/tool-side-effects.ts +15 -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__/activation-session-store.test.ts +41 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
- package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
- package/src/memory/activation-session-store.ts +43 -0
- package/src/memory/conversation-crud.ts +29 -19
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/db-init.ts +6 -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/273-onboarding-events-funnel-columns.ts +46 -0
- package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/onboarding-events-store.ts +66 -18
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
- package/src/memory/v2/__tests__/page-store.test.ts +22 -0
- package/src/memory/v2/consolidation-job.ts +2 -63
- package/src/memory/v2/types.ts +5 -0
- package/src/messaging/providers/telegram-bot/api.ts +14 -5
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +9 -2
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +40 -11
- package/src/plugin-api/types.ts +108 -0
- package/src/plugins/defaults/compaction/compact.ts +106 -0
- package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/plugins/defaults/compaction/manager-store.ts +57 -0
- package/src/plugins/defaults/compaction/package.json +1 -2
- package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
- 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 +156 -26
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
- 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/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
- package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
- package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
- package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
- package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
- package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
- package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
- package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -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/external-api.ts +12 -2
- 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/system-prompt.ts +61 -10
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
- package/src/providers/anthropic/client.ts +9 -10
- package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
- package/src/providers/inference/logit-bias.ts +55 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -1
- package/src/providers/retry.ts +22 -0
- package/src/providers/types.ts +6 -0
- 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 +466 -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__/stt-routes.test.ts +112 -0
- 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 +185 -32
- package/src/runtime/routes/acp-routes.ts +328 -30
- package/src/runtime/routes/app-management-routes.ts +3 -0
- 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/channel-verification-routes.ts +14 -5
- 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 +30 -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/inbound-stages/acl-enforcement.ts +33 -34
- 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 +65 -305
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +337 -35
- package/src/runtime/routes/stt-routes.ts +45 -12
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/routes/workspace-routes.ts +50 -15
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/services/published-app-updater.ts +30 -8
- package/src/signals/cancel.ts +2 -4
- package/src/subagent/manager.ts +21 -5
- package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
- package/src/telemetry/activation-funnel.ts +167 -0
- package/src/telemetry/types.ts +13 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
- package/src/telemetry/usage-telemetry-reporter.ts +26 -1
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +9 -19
- package/src/tools/acp/list-agents.ts +3 -15
- package/src/tools/acp/spawn.test.ts +173 -202
- 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/browser/browser-execution.ts +12 -2
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/network/web-fetch.ts +65 -24
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/ui-surface/definitions.ts +32 -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/daemon/wake-target-adapter.ts +0 -253
- package/src/memory/v3/__tests__/assign.test.ts +0 -242
- package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
- package/src/memory/v3/__tests__/core.test.ts +0 -39
- package/src/memory/v3/__tests__/health.test.ts +0 -219
- package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
- package/src/memory/v3/__tests__/needle.test.ts +0 -107
- package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
- package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
- package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
- package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
- package/src/memory/v3/__tests__/router.test.ts +0 -332
- package/src/memory/v3/__tests__/selector.test.ts +0 -470
- package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
- package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
- package/src/memory/v3/__tests__/tree.test.ts +0 -192
- package/src/memory/v3/assign.ts +0 -268
- package/src/memory/v3/capabilities.ts +0 -124
- package/src/memory/v3/core.ts +0 -26
- package/src/memory/v3/health.ts +0 -0
- package/src/memory/v3/maintain-job.ts +0 -314
- package/src/memory/v3/needle.ts +0 -115
- package/src/memory/v3/orchestrate.ts +0 -126
- package/src/memory/v3/page-content.ts +0 -34
- package/src/memory/v3/provider-blocks.ts +0 -26
- package/src/memory/v3/reconcile.ts +0 -523
- package/src/memory/v3/router.ts +0 -190
- package/src/memory/v3/selection-log-store.ts +0 -84
- package/src/memory/v3/selector.ts +0 -226
- package/src/memory/v3/shadow-plugin.ts +0 -411
- package/src/memory/v3/snapshot.ts +0 -209
- package/src/memory/v3/tree.ts +0 -174
- package/src/memory/v3/types.ts +0 -59
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- 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/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
- 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/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/util/map-limit.ts +0 -27
- /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__/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}/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}/llm-retry.ts +0 -0
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for
|
|
2
|
+
* Tests for ACP route handlers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `?
|
|
4
|
+
* `GET /v1/acp/sessions`: the handler merges in-memory
|
|
5
|
+
* `AcpSessionManager.getStatus()` output with persisted
|
|
6
|
+
* `acp_session_history` rows, deduping by id (in-memory wins), filtering by
|
|
7
|
+
* `?conversationId`, sorting newest-first, and truncating to `?limit`
|
|
8
|
+
* (default 50, max 500).
|
|
9
|
+
*
|
|
10
|
+
* `POST /v1/acp/spawn`: when the adapter binary is missing, the handler
|
|
11
|
+
* silently auto-installs allowlisted adapter packages via a sandboxed `bun`
|
|
12
|
+
* global install before failing with the install hint. `execFile` is stubbed
|
|
13
|
+
* via the shared `installExecFileStub` helper so tests can script
|
|
14
|
+
* `bun add --global` outcomes.
|
|
8
15
|
*/
|
|
9
16
|
|
|
10
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
17
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
18
|
+
|
|
19
|
+
import { installAcpConfigStub } from "../../../acp/__tests__/helpers/acp-config-stub.js";
|
|
20
|
+
import { installExecFileStub } from "../../../acp/__tests__/helpers/exec-file-stub.js";
|
|
21
|
+
import { installWhichStub } from "../../../acp/__tests__/helpers/which-stub.js";
|
|
11
22
|
|
|
12
23
|
mock.module("../../../util/logger.js", () => ({
|
|
13
24
|
getLogger: () =>
|
|
@@ -16,6 +27,12 @@ mock.module("../../../util/logger.js", () => ({
|
|
|
16
27
|
}),
|
|
17
28
|
}));
|
|
18
29
|
|
|
30
|
+
const {
|
|
31
|
+
execScripts,
|
|
32
|
+
execFileMock,
|
|
33
|
+
reset: resetExecFileStub,
|
|
34
|
+
} = installExecFileStub();
|
|
35
|
+
|
|
19
36
|
// ---------------------------------------------------------------------------
|
|
20
37
|
// Stub the ACP session manager so tests control the in-memory side without
|
|
21
38
|
// spawning real child processes. The route handler imports
|
|
@@ -37,64 +54,90 @@ interface FakeSessionState {
|
|
|
37
54
|
|
|
38
55
|
let fakeInMemorySessions: FakeSessionState[] = [];
|
|
39
56
|
|
|
57
|
+
const spawnMock = mock(async () => ({
|
|
58
|
+
acpSessionId: "acp-route-session",
|
|
59
|
+
protocolSessionId: "proto-route-session",
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
const defaultSteerOrResumeImpl = async (
|
|
63
|
+
_id: string,
|
|
64
|
+
_instruction: string,
|
|
65
|
+
): Promise<{ resumed: boolean }> => ({ resumed: false });
|
|
66
|
+
let steerOrResumeImpl: (
|
|
67
|
+
id: string,
|
|
68
|
+
instruction: string,
|
|
69
|
+
) => Promise<{ resumed: boolean }> = defaultSteerOrResumeImpl;
|
|
70
|
+
const steerOrResumeMock = mock(
|
|
71
|
+
(id: string, instruction: string, _send: unknown) =>
|
|
72
|
+
steerOrResumeImpl(id, instruction),
|
|
73
|
+
);
|
|
74
|
+
|
|
40
75
|
mock.module("../../../acp/index.js", () => ({
|
|
41
76
|
getAcpSessionManager: () => ({
|
|
42
77
|
getStatus: () => fakeInMemorySessions,
|
|
78
|
+
getActiveAndPendingIds: () => fakeInMemorySessions.map((s) => s.id),
|
|
79
|
+
spawn: spawnMock,
|
|
80
|
+
steerOrResume: steerOrResumeMock,
|
|
43
81
|
}),
|
|
44
82
|
}));
|
|
45
83
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
84
|
+
// Identity env-prep: the credential-broker plumbing it wraps is exercised in
|
|
85
|
+
// its own suite; spawn tests here only care about the resolve/install flow.
|
|
86
|
+
mock.module("../../../acp/prepare-agent-env.js", () => ({
|
|
87
|
+
prepareAgentEnv: async (agentConfig: unknown) => agentConfig,
|
|
88
|
+
}));
|
|
49
89
|
|
|
50
|
-
|
|
90
|
+
// The spawn route and steer's resume branch gate on a high-risk approval
|
|
91
|
+
// (ATL-822) before starting the host agent. These tests pin the
|
|
92
|
+
// resolve/install/resume flow, so the hub mock auto-resolves the freshly
|
|
93
|
+
// registered confirmation the same way `POST /v1/confirm` would (resolve +
|
|
94
|
+
// directResolve). `approvalBehavior` flips allow/deny; `confirmationRequests`
|
|
95
|
+
// captures the prompts. Other event types are ignored.
|
|
96
|
+
import * as pendingInteractions from "../../../runtime/pending-interactions.js";
|
|
51
97
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
98
|
+
let approvalBehavior: "allow" | "deny" = "allow";
|
|
99
|
+
const confirmationRequests: Array<Record<string, unknown>> = [];
|
|
100
|
+
const broadcasts: Array<Record<string, unknown>> = [];
|
|
55
101
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
stopReason?: string | null;
|
|
65
|
-
error?: string | null;
|
|
66
|
-
eventLogJson?: string;
|
|
67
|
-
}): void {
|
|
68
|
-
getSqlite()
|
|
69
|
-
.query(
|
|
70
|
-
/*sql*/ `
|
|
71
|
-
INSERT INTO acp_session_history (
|
|
72
|
-
id,
|
|
73
|
-
agent_id,
|
|
74
|
-
acp_session_id,
|
|
75
|
-
parent_conversation_id,
|
|
76
|
-
started_at,
|
|
77
|
-
completed_at,
|
|
78
|
-
status,
|
|
79
|
-
stop_reason,
|
|
80
|
-
error,
|
|
81
|
-
event_log_json
|
|
82
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
83
|
-
`,
|
|
84
|
-
)
|
|
85
|
-
.run(
|
|
86
|
-
row.id,
|
|
87
|
-
row.agentId,
|
|
88
|
-
row.acpSessionId,
|
|
89
|
-
row.parentConversationId,
|
|
90
|
-
row.startedAt,
|
|
91
|
-
row.completedAt ?? null,
|
|
92
|
-
row.status,
|
|
93
|
-
row.stopReason ?? null,
|
|
94
|
-
row.error ?? null,
|
|
95
|
-
row.eventLogJson ?? "[]",
|
|
102
|
+
mock.module("../../../runtime/assistant-event-hub.js", () => ({
|
|
103
|
+
broadcastMessage: (msg: { type?: string; requestId?: string }) => {
|
|
104
|
+
broadcasts.push(msg as Record<string, unknown>);
|
|
105
|
+
if (msg?.type !== "confirmation_request") return;
|
|
106
|
+
confirmationRequests.push(msg as Record<string, unknown>);
|
|
107
|
+
const interaction = pendingInteractions.resolve(
|
|
108
|
+
msg.requestId as string,
|
|
109
|
+
approvalBehavior === "allow" ? "approved" : "rejected",
|
|
96
110
|
);
|
|
97
|
-
|
|
111
|
+
interaction?.directResolve?.(approvalBehavior);
|
|
112
|
+
},
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
/** Drain pending micro/macrotasks so background resume work settles. */
|
|
116
|
+
const flushAsync = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
117
|
+
|
|
118
|
+
const config = await installAcpConfigStub();
|
|
119
|
+
const which = installWhichStub();
|
|
120
|
+
|
|
121
|
+
import {
|
|
122
|
+
clearHistory,
|
|
123
|
+
insertHistoryRow,
|
|
124
|
+
} from "../../../acp/__tests__/helpers/acp-history-db.js";
|
|
125
|
+
import {
|
|
126
|
+
AcpResumeError,
|
|
127
|
+
AcpSessionNotFoundError,
|
|
128
|
+
} from "../../../acp/session-manager.js";
|
|
129
|
+
import { initializeDb } from "../../../memory/db-init.js";
|
|
130
|
+
import { FailedDependencyError, NotFoundError } from "../errors.js";
|
|
131
|
+
|
|
132
|
+
const { ROUTES } = await import("../acp-routes.js");
|
|
133
|
+
const { _resetAdapterInstallCacheForTests } =
|
|
134
|
+
await import("../../../acp/auto-install.js");
|
|
135
|
+
|
|
136
|
+
initializeDb();
|
|
137
|
+
|
|
138
|
+
afterAll(() => {
|
|
139
|
+
which.restore();
|
|
140
|
+
});
|
|
98
141
|
|
|
99
142
|
function getSessionsHandler() {
|
|
100
143
|
const route = ROUTES.find(
|
|
@@ -122,6 +165,16 @@ interface ResponseShape {
|
|
|
122
165
|
beforeEach(() => {
|
|
123
166
|
fakeInMemorySessions = [];
|
|
124
167
|
clearHistory();
|
|
168
|
+
resetExecFileStub();
|
|
169
|
+
spawnMock.mockClear();
|
|
170
|
+
steerOrResumeMock.mockClear();
|
|
171
|
+
steerOrResumeImpl = defaultSteerOrResumeImpl;
|
|
172
|
+
_resetAdapterInstallCacheForTests();
|
|
173
|
+
config.setConfig({});
|
|
174
|
+
which.setWhich((cmd) => `/usr/local/bin/${cmd}`);
|
|
175
|
+
approvalBehavior = "allow";
|
|
176
|
+
confirmationRequests.length = 0;
|
|
177
|
+
broadcasts.length = 0;
|
|
125
178
|
});
|
|
126
179
|
|
|
127
180
|
describe("GET /v1/acp/sessions — merged in-memory + history", () => {
|
|
@@ -306,7 +359,9 @@ describe("GET /v1/acp/sessions — merged in-memory + history", () => {
|
|
|
306
359
|
});
|
|
307
360
|
|
|
308
361
|
const handler = getSessionsHandler();
|
|
309
|
-
const body = (await handler({
|
|
362
|
+
const body = (await handler({
|
|
363
|
+
queryParams: { limit: "2" },
|
|
364
|
+
})) as ResponseShape;
|
|
310
365
|
expect(body.sessions).toHaveLength(2);
|
|
311
366
|
expect(body.sessions.map((s) => s.id)).toEqual(["live-newest", "hist-mid"]);
|
|
312
367
|
});
|
|
@@ -392,3 +447,359 @@ describe("GET /v1/acp/sessions — merged in-memory + history", () => {
|
|
|
392
447
|
expect(body.sessions).toHaveLength(3);
|
|
393
448
|
});
|
|
394
449
|
});
|
|
450
|
+
|
|
451
|
+
// ---------------------------------------------------------------------------
|
|
452
|
+
// POST /v1/acp/spawn: auto-install on missing adapter binary
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
|
|
455
|
+
function getSpawnHandler() {
|
|
456
|
+
const route = ROUTES.find(
|
|
457
|
+
(r) => r.endpoint === "acp/spawn" && r.method === "POST",
|
|
458
|
+
);
|
|
459
|
+
if (!route) throw new Error("acp/spawn POST route not found");
|
|
460
|
+
return route.handler;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const SPAWN_BODY = {
|
|
464
|
+
agent: "claude",
|
|
465
|
+
task: "do something",
|
|
466
|
+
conversationId: "conv-1",
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const BUN_BIN = "/usr/local/bin/bun";
|
|
470
|
+
const BUN_ADD_KEY = `${BUN_BIN} add`;
|
|
471
|
+
|
|
472
|
+
describe("POST /v1/acp/spawn: sandboxed bun auto-install on missing binary", () => {
|
|
473
|
+
test("known command + bun present: installs via bun, then spawns the real binary", async () => {
|
|
474
|
+
// Binary appears on PATH only after `bun add --global` runs, simulating a
|
|
475
|
+
// successful global install that links the adapter bin onto PATH.
|
|
476
|
+
let binaryOnPath = false;
|
|
477
|
+
which.setWhich((cmd) => {
|
|
478
|
+
if (cmd === "bun") return BUN_BIN;
|
|
479
|
+
if (binaryOnPath) return `/usr/local/bin/${cmd}`;
|
|
480
|
+
return null;
|
|
481
|
+
});
|
|
482
|
+
execScripts.set(BUN_ADD_KEY, {
|
|
483
|
+
stdout: "",
|
|
484
|
+
onCall: () => {
|
|
485
|
+
binaryOnPath = true;
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const handler = getSpawnHandler();
|
|
490
|
+
const body = (await handler({ body: SPAWN_BODY })) as Record<
|
|
491
|
+
string,
|
|
492
|
+
unknown
|
|
493
|
+
>;
|
|
494
|
+
|
|
495
|
+
expect(body).toEqual({
|
|
496
|
+
acpSessionId: "acp-route-session",
|
|
497
|
+
protocolSessionId: "proto-route-session",
|
|
498
|
+
agent: "claude",
|
|
499
|
+
});
|
|
500
|
+
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
501
|
+
// The real adapter binary is spawned, not a `bun x` wrapper.
|
|
502
|
+
const agentConfigArg = (spawnMock.mock.calls[0] as unknown[])[1] as {
|
|
503
|
+
command: string;
|
|
504
|
+
};
|
|
505
|
+
expect(agentConfigArg.command).toBe("claude-agent-acp");
|
|
506
|
+
// Exactly one install, and it was `bun add --global` (never npm).
|
|
507
|
+
expect(execFileMock).toHaveBeenCalledTimes(1);
|
|
508
|
+
const [command, args] = execFileMock.mock.calls[0];
|
|
509
|
+
expect(command).toBe(BUN_BIN);
|
|
510
|
+
expect(args).toEqual([
|
|
511
|
+
"add",
|
|
512
|
+
"--global",
|
|
513
|
+
"@agentclientprotocol/claude-agent-acp",
|
|
514
|
+
]);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test("install runs in a temp dir (not the task cwd) with secrets stripped", async () => {
|
|
518
|
+
let binaryOnPath = false;
|
|
519
|
+
which.setWhich((cmd) => {
|
|
520
|
+
if (cmd === "bun") return BUN_BIN;
|
|
521
|
+
if (binaryOnPath) return `/usr/local/bin/${cmd}`;
|
|
522
|
+
return null;
|
|
523
|
+
});
|
|
524
|
+
execScripts.set(BUN_ADD_KEY, {
|
|
525
|
+
stdout: "",
|
|
526
|
+
onCall: () => {
|
|
527
|
+
binaryOnPath = true;
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
const handler = getSpawnHandler();
|
|
532
|
+
await handler({ body: { ...SPAWN_BODY, cwd: "/untrusted/project" } });
|
|
533
|
+
|
|
534
|
+
const options = execFileMock.mock.calls[0][2] as {
|
|
535
|
+
cwd?: string;
|
|
536
|
+
env?: NodeJS.ProcessEnv;
|
|
537
|
+
};
|
|
538
|
+
expect(options.cwd).not.toBe("/untrusted/project");
|
|
539
|
+
expect(options.cwd).toContain("vellum-acp-install-");
|
|
540
|
+
expect(options.env?.CLAUDE_CODE_OAUTH_TOKEN).toBeUndefined();
|
|
541
|
+
expect(options.env?.GEMINI_API_KEY).toBeUndefined();
|
|
542
|
+
expect(options.env?.BUN_CONFIG_REGISTRY).toBe(
|
|
543
|
+
"https://registry.npmjs.org/",
|
|
544
|
+
);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test("bun absent: no install attempted, FailedDependencyError with the hint", async () => {
|
|
548
|
+
which.setWhich({}); // neither bun nor the adapter on PATH
|
|
549
|
+
|
|
550
|
+
const handler = getSpawnHandler();
|
|
551
|
+
const promise = handler({ body: SPAWN_BODY });
|
|
552
|
+
await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
|
|
553
|
+
await expect(promise).rejects.toThrow(
|
|
554
|
+
/claude-agent-acp is not on PATH.*bun add -g @agentclientprotocol\/claude-agent-acp/,
|
|
555
|
+
);
|
|
556
|
+
expect(execFileMock).not.toHaveBeenCalled();
|
|
557
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test("install failure: FailedDependencyError carries hint and failure reason, never npm", async () => {
|
|
561
|
+
which.setWhich({ bun: BUN_BIN });
|
|
562
|
+
execScripts.set(BUN_ADD_KEY, {
|
|
563
|
+
error: new Error("EACCES: permission denied"),
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const handler = getSpawnHandler();
|
|
567
|
+
const promise = handler({ body: SPAWN_BODY });
|
|
568
|
+
await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
|
|
569
|
+
await expect(promise).rejects.toThrow(
|
|
570
|
+
/claude-agent-acp is not on PATH.*bun add -g @agentclientprotocol\/claude-agent-acp.*auto-install failed.*EACCES/,
|
|
571
|
+
);
|
|
572
|
+
for (const call of execFileMock.mock.calls) {
|
|
573
|
+
expect(call[0]).not.toBe("npm");
|
|
574
|
+
}
|
|
575
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test("unknown command: plain hint maps to FailedDependencyError", async () => {
|
|
579
|
+
// The allowlist itself (no npm invocation for unmapped commands) is
|
|
580
|
+
// pinned in auto-install.test.ts and spawn.test.ts; this asserts only
|
|
581
|
+
// the route's transport mapping of the plain-hint failure.
|
|
582
|
+
config.setConfig({
|
|
583
|
+
agents: { custom: { command: "custom-bin", args: [] } },
|
|
584
|
+
});
|
|
585
|
+
which.setWhich({});
|
|
586
|
+
|
|
587
|
+
const handler = getSpawnHandler();
|
|
588
|
+
const promise = handler({ body: { ...SPAWN_BODY, agent: "custom" } });
|
|
589
|
+
await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
|
|
590
|
+
await expect(promise).rejects.toThrow(
|
|
591
|
+
"custom-bin is not on PATH. Install 'custom-bin' and ensure it is on PATH.",
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// ---------------------------------------------------------------------------
|
|
597
|
+
// POST /v1/acp/:id/steer: transparent resume of sessions not in memory
|
|
598
|
+
// ---------------------------------------------------------------------------
|
|
599
|
+
|
|
600
|
+
function getSteerHandler() {
|
|
601
|
+
const route = ROUTES.find(
|
|
602
|
+
(r) => r.endpoint === "acp/:id/steer" && r.method === "POST",
|
|
603
|
+
);
|
|
604
|
+
if (!route) throw new Error("acp/:id/steer POST route not found");
|
|
605
|
+
return route.handler;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
describe("POST /v1/acp/:id/steer: resume fallback", () => {
|
|
609
|
+
test("in-memory session steers without a resume", async () => {
|
|
610
|
+
const handler = getSteerHandler();
|
|
611
|
+
const body = await handler({
|
|
612
|
+
pathParams: { id: "live-1" },
|
|
613
|
+
body: { instruction: "redirect" },
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
expect(body).toEqual({ acpSessionId: "live-1", steered: true });
|
|
617
|
+
expect(steerOrResumeMock).toHaveBeenCalledTimes(1);
|
|
618
|
+
expect(steerOrResumeMock.mock.calls[0][0]).toBe("live-1");
|
|
619
|
+
expect(steerOrResumeMock.mock.calls[0][1]).toBe("redirect");
|
|
620
|
+
expect(typeof steerOrResumeMock.mock.calls[0][2]).toBe("function");
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("resumed session reports the resumed flag", async () => {
|
|
624
|
+
steerOrResumeImpl = async () => ({ resumed: true });
|
|
625
|
+
|
|
626
|
+
const handler = getSteerHandler();
|
|
627
|
+
const body = await handler({
|
|
628
|
+
pathParams: { id: "gone-1" },
|
|
629
|
+
body: { instruction: "keep going" },
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
expect(body).toEqual({
|
|
633
|
+
acpSessionId: "gone-1",
|
|
634
|
+
steered: true,
|
|
635
|
+
resumed: true,
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
test("typed not-found (no session, no history row) maps to NotFoundError", async () => {
|
|
640
|
+
steerOrResumeImpl = async (id) => {
|
|
641
|
+
throw new AcpSessionNotFoundError(id);
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
const handler = getSteerHandler();
|
|
645
|
+
const promise = handler({
|
|
646
|
+
pathParams: { id: "missing-1" },
|
|
647
|
+
body: { instruction: "go" },
|
|
648
|
+
});
|
|
649
|
+
await expect(promise).rejects.toBeInstanceOf(NotFoundError);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
test("resume failure surfaces as FailedDependencyError with the actionable hint", async () => {
|
|
653
|
+
steerOrResumeImpl = async (id) => {
|
|
654
|
+
throw new AcpResumeError(
|
|
655
|
+
new Error(
|
|
656
|
+
`ACP session "${id}" was recorded before resume support ` +
|
|
657
|
+
`(no working directory persisted) and cannot be resumed. ` +
|
|
658
|
+
`Spawn a new session instead.`,
|
|
659
|
+
),
|
|
660
|
+
);
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
const handler = getSteerHandler();
|
|
664
|
+
const promise = handler({
|
|
665
|
+
pathParams: { id: "legacy-1" },
|
|
666
|
+
body: { instruction: "go" },
|
|
667
|
+
});
|
|
668
|
+
await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
|
|
669
|
+
await expect(promise).rejects.toThrow(/recorded before resume support/);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
test("plain steer errors map to NotFoundError", async () => {
|
|
673
|
+
steerOrResumeImpl = async (id) => {
|
|
674
|
+
throw new Error(
|
|
675
|
+
`ACP session "${id}" is not running (status: initializing)`,
|
|
676
|
+
);
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
const handler = getSteerHandler();
|
|
680
|
+
const promise = handler({
|
|
681
|
+
pathParams: { id: "init-1" },
|
|
682
|
+
body: { instruction: "go" },
|
|
683
|
+
});
|
|
684
|
+
await expect(promise).rejects.toBeInstanceOf(NotFoundError);
|
|
685
|
+
});
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// ---------------------------------------------------------------------------
|
|
689
|
+
// POST /v1/acp/:id/steer: resume crosses the host-spawn boundary, so it is
|
|
690
|
+
// gated by the same high-risk guardian approval as spawn (ATL-822). Steering
|
|
691
|
+
// a session still active in memory only redirects an already-approved live
|
|
692
|
+
// process and is NOT prompted.
|
|
693
|
+
// ---------------------------------------------------------------------------
|
|
694
|
+
|
|
695
|
+
describe("POST /v1/acp/:id/steer: resume approval gate", () => {
|
|
696
|
+
test("acks immediately with approvalPending, then resumes once granted", async () => {
|
|
697
|
+
insertHistoryRow({
|
|
698
|
+
id: "gone-1",
|
|
699
|
+
agentId: "claude",
|
|
700
|
+
parentConversationId: "conv-z",
|
|
701
|
+
status: "completed",
|
|
702
|
+
cwd: "/work/repo",
|
|
703
|
+
});
|
|
704
|
+
steerOrResumeImpl = async () => ({ resumed: true });
|
|
705
|
+
|
|
706
|
+
const handler = getSteerHandler();
|
|
707
|
+
// The ack returns before the (async) resume completes, so a slow guardian
|
|
708
|
+
// approval can't trip the client's short ack timeout (ATL-822 / Codex P2).
|
|
709
|
+
const body = await handler({
|
|
710
|
+
pathParams: { id: "gone-1" },
|
|
711
|
+
body: { instruction: "keep going" },
|
|
712
|
+
});
|
|
713
|
+
expect(body).toEqual({
|
|
714
|
+
acpSessionId: "gone-1",
|
|
715
|
+
steered: false,
|
|
716
|
+
approvalPending: true,
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
// The high-risk prompt is surfaced for the resume.
|
|
720
|
+
expect(confirmationRequests).toHaveLength(1);
|
|
721
|
+
const prompt = confirmationRequests[0];
|
|
722
|
+
expect(prompt.toolName).toBe("acp_steer");
|
|
723
|
+
expect(prompt.riskLevel).toBe("high");
|
|
724
|
+
expect(prompt.executionTarget).toBe("host");
|
|
725
|
+
expect(prompt.conversationId).toBe("conv-z");
|
|
726
|
+
expect((prompt.input as { cwd?: string }).cwd).toBe("/work/repo");
|
|
727
|
+
|
|
728
|
+
// After approval settles, the background worker performs the resume.
|
|
729
|
+
await flushAsync();
|
|
730
|
+
expect(steerOrResumeMock).toHaveBeenCalledTimes(1);
|
|
731
|
+
expect(steerOrResumeMock.mock.calls[0][0]).toBe("gone-1");
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
test("denied resume never reaches the session manager and reports an error event", async () => {
|
|
735
|
+
approvalBehavior = "deny";
|
|
736
|
+
insertHistoryRow({
|
|
737
|
+
id: "gone-2",
|
|
738
|
+
acpSessionId: "proto-gone-2",
|
|
739
|
+
parentConversationId: "conv-z",
|
|
740
|
+
status: "completed",
|
|
741
|
+
cwd: "/work/repo",
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
const handler = getSteerHandler();
|
|
745
|
+
const body = await handler({
|
|
746
|
+
pathParams: { id: "gone-2" },
|
|
747
|
+
body: { instruction: "do evil" },
|
|
748
|
+
});
|
|
749
|
+
expect(body).toEqual({
|
|
750
|
+
acpSessionId: "gone-2",
|
|
751
|
+
steered: false,
|
|
752
|
+
approvalPending: true,
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
await flushAsync();
|
|
756
|
+
// Denied before any host re-spawn, and the denial surfaces over SSE.
|
|
757
|
+
expect(steerOrResumeMock).not.toHaveBeenCalled();
|
|
758
|
+
expect(pendingInteractions.getAll()).toHaveLength(0);
|
|
759
|
+
// Keyed by the daemon/route id (what SSE consumers index by), not the
|
|
760
|
+
// persisted protocol id.
|
|
761
|
+
const errEvent = broadcasts.find((m) => m.type === "acp_session_error");
|
|
762
|
+
expect(errEvent?.acpSessionId).toBe("gone-2");
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
test("a legacy row without a persisted cwd is not resumable, so no prompt", async () => {
|
|
766
|
+
insertHistoryRow({ id: "legacy-2", status: "completed", cwd: null });
|
|
767
|
+
steerOrResumeImpl = async (id) => {
|
|
768
|
+
throw new AcpResumeError(
|
|
769
|
+
new Error(`ACP session "${id}" cannot be resumed.`),
|
|
770
|
+
);
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
const handler = getSteerHandler();
|
|
774
|
+
await expect(
|
|
775
|
+
handler({ pathParams: { id: "legacy-2" }, body: { instruction: "go" } }),
|
|
776
|
+
).rejects.toBeInstanceOf(FailedDependencyError);
|
|
777
|
+
// No spawn would occur, so the gate stays out of the way.
|
|
778
|
+
expect(confirmationRequests).toHaveLength(0);
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
test("steering a session active in memory is not prompted", async () => {
|
|
782
|
+
fakeInMemorySessions = [
|
|
783
|
+
{
|
|
784
|
+
id: "live-2",
|
|
785
|
+
agentId: "claude",
|
|
786
|
+
acpSessionId: "proto-live-2",
|
|
787
|
+
parentConversationId: "conv-1",
|
|
788
|
+
status: "running",
|
|
789
|
+
startedAt: 1000,
|
|
790
|
+
},
|
|
791
|
+
];
|
|
792
|
+
// A resumable row also exists, but the in-memory session wins → steer.
|
|
793
|
+
insertHistoryRow({ id: "live-2", status: "completed", cwd: "/work/repo" });
|
|
794
|
+
|
|
795
|
+
const handler = getSteerHandler();
|
|
796
|
+
const body = await handler({
|
|
797
|
+
pathParams: { id: "live-2" },
|
|
798
|
+
body: { instruction: "redirect" },
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
expect(confirmationRequests).toHaveLength(0);
|
|
802
|
+
expect(body).toEqual({ acpSessionId: "live-2", steered: true });
|
|
803
|
+
expect(steerOrResumeMock).toHaveBeenCalledTimes(1);
|
|
804
|
+
});
|
|
805
|
+
});
|
|
@@ -23,7 +23,7 @@ mock.module("../../assistant-event-hub.js", () => ({
|
|
|
23
23
|
broadcastMessage: () => {},
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
-
import { findConversation } from "../../../daemon/conversation-
|
|
26
|
+
import { findConversation } from "../../../daemon/conversation-registry.js";
|
|
27
27
|
import { createConversation } from "../../../memory/conversation-crud.js";
|
|
28
28
|
import { getDb } from "../../../memory/db-connection.js";
|
|
29
29
|
import { initializeDb } from "../../../memory/db-init.js";
|