@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,3 +1,8 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { writeFileSync } from "node:fs";
|
|
3
|
+
import { unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
1
6
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
7
|
|
|
3
8
|
// ---------------------------------------------------------------------------
|
|
@@ -26,6 +31,35 @@ mock.module("../../../providers/speech-to-text/resolve.js", () => ({
|
|
|
26
31
|
},
|
|
27
32
|
}));
|
|
28
33
|
|
|
34
|
+
// -- Spawn mock (ffmpeg/ffprobe) for the file-transcription path ------------
|
|
35
|
+
|
|
36
|
+
type SpawnResult = { exitCode: number; stdout: string; stderr: string };
|
|
37
|
+
let spawnOverride: ((args: string[]) => SpawnResult) | null = null;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Default spawn behavior: ffmpeg writes the requested output file so the
|
|
41
|
+
* downstream readFile succeeds, ffprobe reports a short duration.
|
|
42
|
+
*/
|
|
43
|
+
function defaultSpawn(args: string[]): SpawnResult {
|
|
44
|
+
if (args[0] === "ffmpeg") {
|
|
45
|
+
const outputPath = args[args.length - 1];
|
|
46
|
+
writeFileSync(outputPath, Buffer.from("fake-wav-bytes"));
|
|
47
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
48
|
+
}
|
|
49
|
+
if (args[0] === "ffprobe") {
|
|
50
|
+
return { exitCode: 0, stdout: "1.0", stderr: "" };
|
|
51
|
+
}
|
|
52
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
mock.module("../../../util/spawn.js", () => ({
|
|
56
|
+
FFMPEG_TRANSCODE_TIMEOUT_MS: 60_000,
|
|
57
|
+
FFPROBE_TIMEOUT_MS: 10_000,
|
|
58
|
+
STT_REQUEST_TIMEOUT_MS: 300_000,
|
|
59
|
+
spawnWithTimeout: async (args: string[]) =>
|
|
60
|
+
(spawnOverride ?? defaultSpawn)(args),
|
|
61
|
+
}));
|
|
62
|
+
|
|
29
63
|
// ---------------------------------------------------------------------------
|
|
30
64
|
// Import under test — after mocks
|
|
31
65
|
// ---------------------------------------------------------------------------
|
|
@@ -86,6 +120,7 @@ const fakeTranscriber: BatchTranscriber = {
|
|
|
86
120
|
beforeEach(() => {
|
|
87
121
|
mockTranscriber = fakeTranscriber;
|
|
88
122
|
mockResolveError = null;
|
|
123
|
+
spawnOverride = null;
|
|
89
124
|
});
|
|
90
125
|
|
|
91
126
|
afterEach(() => {
|
|
@@ -366,4 +401,81 @@ describe("stt-routes", () => {
|
|
|
366
401
|
"BAD_GATEWAY",
|
|
367
402
|
);
|
|
368
403
|
});
|
|
404
|
+
|
|
405
|
+
// -- File transcription: provider error categorization --------------------
|
|
406
|
+
|
|
407
|
+
async function withTempAudioFile(
|
|
408
|
+
run: (filePath: string) => Promise<void>,
|
|
409
|
+
): Promise<void> {
|
|
410
|
+
const filePath = join(tmpdir(), `vellum-stt-test-${randomUUID()}.wav`);
|
|
411
|
+
await writeFile(filePath, Buffer.from("fake-audio"));
|
|
412
|
+
try {
|
|
413
|
+
await run(filePath);
|
|
414
|
+
} finally {
|
|
415
|
+
await unlink(filePath).catch(() => {});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
test("transcribe-file maps provider auth failures (403) to 401", async () => {
|
|
420
|
+
// Reproduces JARVIS-994: a raw Gemini 403 denial must surface as an
|
|
421
|
+
// actionable 401, not a generic 502, and must not be treated as a
|
|
422
|
+
// daemon-level error.
|
|
423
|
+
mockTranscriber = {
|
|
424
|
+
providerId: "google-gemini",
|
|
425
|
+
boundaryId: "daemon-batch",
|
|
426
|
+
transcribe: async () => {
|
|
427
|
+
throw new Error(
|
|
428
|
+
'Google Gemini API error (403): {"error":{"code":403,"message":"Your project has been denied access. Please contact support.","status":"PERMISSION_DENIED"}}',
|
|
429
|
+
);
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
await withTempAudioFile(async (filePath) => {
|
|
434
|
+
const { handler } = getRoute("stt/transcribe-file");
|
|
435
|
+
const err = await expectRouteError(
|
|
436
|
+
() => handler(makeArgs({ filePath })),
|
|
437
|
+
401,
|
|
438
|
+
"UNAUTHORIZED",
|
|
439
|
+
);
|
|
440
|
+
expect(err.message).toContain("credentials");
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test("transcribe-file maps generic provider failures to 502", async () => {
|
|
445
|
+
mockTranscriber = {
|
|
446
|
+
providerId: "openai-whisper",
|
|
447
|
+
boundaryId: "daemon-batch",
|
|
448
|
+
transcribe: async () => {
|
|
449
|
+
throw new Error("upstream kaboom");
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
await withTempAudioFile(async (filePath) => {
|
|
454
|
+
const { handler } = getRoute("stt/transcribe-file");
|
|
455
|
+
await expectRouteError(
|
|
456
|
+
() => handler(makeArgs({ filePath })),
|
|
457
|
+
502,
|
|
458
|
+
"BAD_GATEWAY",
|
|
459
|
+
);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test("transcribe-file maps ffmpeg failures to 502 even when stderr contains a status-like number", async () => {
|
|
464
|
+
// Guards against ffmpeg stderr (or a user-supplied path) deciding the HTTP
|
|
465
|
+
// category: a conversion failure whose message contains "403" must stay a
|
|
466
|
+
// 502 conversion error, not become a misleading 401 auth error.
|
|
467
|
+
spawnOverride = (args) =>
|
|
468
|
+
args[0] === "ffmpeg"
|
|
469
|
+
? { exitCode: 1, stdout: "", stderr: "Error opening /tmp/403/clip.wav" }
|
|
470
|
+
: { exitCode: 0, stdout: "1.0", stderr: "" };
|
|
471
|
+
|
|
472
|
+
await withTempAudioFile(async (filePath) => {
|
|
473
|
+
const { handler } = getRoute("stt/transcribe-file");
|
|
474
|
+
await expectRouteError(
|
|
475
|
+
() => handler(makeArgs({ filePath })),
|
|
476
|
+
502,
|
|
477
|
+
"BAD_GATEWAY",
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
369
481
|
});
|
|
@@ -42,7 +42,7 @@ const findBySurfaceCalls: string[] = [];
|
|
|
42
42
|
const getOrCreateCalls: string[] = [];
|
|
43
43
|
const rawGetCalls: Array<{ sql: string; params: unknown[] }> = [];
|
|
44
44
|
|
|
45
|
-
mock.module("../../../daemon/conversation-
|
|
45
|
+
mock.module("../../../daemon/conversation-registry.js", () => ({
|
|
46
46
|
findConversation: (id: string) => {
|
|
47
47
|
findConvCalls.push(id);
|
|
48
48
|
return memoryById ?? undefined;
|
|
@@ -51,6 +51,9 @@ mock.module("../../../daemon/conversation-store.js", () => ({
|
|
|
51
51
|
findBySurfaceCalls.push(surfaceId);
|
|
52
52
|
return memoryBySurface ?? undefined;
|
|
53
53
|
},
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
mock.module("../../../daemon/conversation-store.js", () => ({
|
|
54
57
|
getOrCreateConversation: async (id: string) => {
|
|
55
58
|
getOrCreateCalls.push(id);
|
|
56
59
|
if (!rehydrated) {
|
|
@@ -183,9 +186,7 @@ describe("triggerSurfaceAction handler", () => {
|
|
|
183
186
|
// SQL must filter the messages table by ui_surface payload pattern.
|
|
184
187
|
expect(rawGetCalls[0]!.sql).toContain("FROM messages");
|
|
185
188
|
expect(rawGetCalls[0]!.sql).toContain("LIKE");
|
|
186
|
-
expect(rawGetCalls[0]!.params).toEqual([
|
|
187
|
-
`%"surfaceId":"surf-evicted"%`,
|
|
188
|
-
]);
|
|
189
|
+
expect(rawGetCalls[0]!.params).toEqual([`%"surfaceId":"surf-evicted"%`]);
|
|
189
190
|
expect(getOrCreateCalls).toEqual(["conv-from-db"]);
|
|
190
191
|
expect(rehydrated.surfaceActionCalls).toEqual([
|
|
191
192
|
{ surfaceId: "surf-evicted", actionId: "act-3", data: undefined },
|
|
@@ -51,7 +51,7 @@ const findBySurfaceCalls: string[] = [];
|
|
|
51
51
|
const getOrCreateCalls: string[] = [];
|
|
52
52
|
const rawGetCalls: Array<{ sql: string; params: unknown[] }> = [];
|
|
53
53
|
|
|
54
|
-
mock.module("../../../daemon/conversation-
|
|
54
|
+
mock.module("../../../daemon/conversation-registry.js", () => ({
|
|
55
55
|
findConversation: (id: string) => {
|
|
56
56
|
findConvCalls.push(id);
|
|
57
57
|
return memoryById ?? undefined;
|
|
@@ -60,6 +60,9 @@ mock.module("../../../daemon/conversation-store.js", () => ({
|
|
|
60
60
|
findBySurfaceCalls.push(surfaceId);
|
|
61
61
|
return undefined;
|
|
62
62
|
},
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
mock.module("../../../daemon/conversation-store.js", () => ({
|
|
63
66
|
getOrCreateConversation: async (id: string) => {
|
|
64
67
|
getOrCreateCalls.push(id);
|
|
65
68
|
if (!rehydrated) {
|
|
@@ -2,25 +2,28 @@
|
|
|
2
2
|
* Tests for the ACP route handlers.
|
|
3
3
|
*
|
|
4
4
|
* Suites:
|
|
5
|
-
* - POST /v1/acp/spawn
|
|
6
|
-
*
|
|
5
|
+
* - POST /v1/acp/spawn: resolver failure paths (unknown_agent) and the
|
|
6
|
+
* body-shape guard. The binary_not_found / auto-install surface is
|
|
7
|
+
* covered in `__tests__/acp-routes.test.ts`.
|
|
7
8
|
* - POST /v1/acp/spawn (env injection) — CLAUDE_CODE_OAUTH_TOKEN is read
|
|
8
9
|
* from the credential broker (policy-gated + audited) and merged into
|
|
9
10
|
* `agentConfig.env` ONLY for the `claude` agent.
|
|
10
11
|
* - DELETE /v1/acp/sessions?status=completed — the bulk-clear route that
|
|
11
12
|
* wipes terminal-state rows (completed/failed/cancelled) from
|
|
12
|
-
* `acp_session_history` while leaving running/initializing rows
|
|
13
|
+
* `acp_session_history` while leaving running/initializing rows and
|
|
14
|
+
* rows with an in-flight resume intact.
|
|
13
15
|
* - DELETE /v1/acp/sessions/:id — single-row delete: completed → 200,
|
|
14
|
-
* running → 409, unknown id → idempotent
|
|
16
|
+
* running or mid-resume → 409, unknown id → idempotent
|
|
17
|
+
* { deleted: false }.
|
|
15
18
|
*
|
|
16
19
|
* The spawn tests mirror the resolver's test setup using the shared
|
|
17
20
|
* `installAcpConfigStub` and `installWhichStub` helpers so the host
|
|
18
21
|
* environment doesn't influence the resolver's PATH preflight.
|
|
19
22
|
*
|
|
20
|
-
* The
|
|
21
|
-
*
|
|
22
|
-
* and use the real DB (initialized via the test preload's
|
|
23
|
-
* workspace) to verify the row is actually removed.
|
|
23
|
+
* The delete tests stub `getAcpSessionManager` so we can drive the
|
|
24
|
+
* in-memory-status and pending-resume checks without spawning real ACP
|
|
25
|
+
* child processes, and use the real DB (initialized via the test preload's
|
|
26
|
+
* per-file workspace) to verify the row is actually removed.
|
|
24
27
|
*/
|
|
25
28
|
|
|
26
29
|
import {
|
|
@@ -36,6 +39,7 @@ import {
|
|
|
36
39
|
import { installAcpConfigStub } from "../../acp/__tests__/helpers/acp-config-stub.js";
|
|
37
40
|
import { installWhichStub } from "../../acp/__tests__/helpers/which-stub.js";
|
|
38
41
|
import type { AcpSessionState } from "../../acp/index.js";
|
|
42
|
+
import * as pendingInteractions from "../pending-interactions.js";
|
|
39
43
|
|
|
40
44
|
const config = await installAcpConfigStub();
|
|
41
45
|
const which = installWhichStub();
|
|
@@ -48,8 +52,10 @@ afterAll(() => {
|
|
|
48
52
|
// in-memory-status check without spawning real ACP processes, and so the
|
|
49
53
|
// env-injection spawn tests can capture the `agentConfig` arg without
|
|
50
54
|
// launching a real subprocess. Stored in mutable state so individual tests
|
|
51
|
-
// can plant arbitrary states / inspect capture.
|
|
55
|
+
// can plant arbitrary states / inspect capture. `pendingResumeIds` models
|
|
56
|
+
// ids reserved by an in-flight resumeFromHistory (no SessionEntry yet).
|
|
52
57
|
const inMemoryStates = new Map<string, AcpSessionState>();
|
|
58
|
+
const pendingResumeIds = new Set<string>();
|
|
53
59
|
|
|
54
60
|
interface CapturedSpawn {
|
|
55
61
|
agent: string;
|
|
@@ -70,6 +76,9 @@ mock.module("../../acp/index.js", () => ({
|
|
|
70
76
|
if (!state) throw new Error(`ACP session "${id}" not found`);
|
|
71
77
|
return state;
|
|
72
78
|
},
|
|
79
|
+
getActiveAndPendingIds: () => [
|
|
80
|
+
...new Set([...inMemoryStates.keys(), ...pendingResumeIds]),
|
|
81
|
+
],
|
|
73
82
|
spawn: async (
|
|
74
83
|
agent: string,
|
|
75
84
|
agentConfig: { env?: Record<string, string> },
|
|
@@ -162,6 +171,31 @@ mock.module("../../tools/credentials/broker.js", () => ({
|
|
|
162
171
|
},
|
|
163
172
|
}));
|
|
164
173
|
|
|
174
|
+
// Drive the spawn route's high-risk approval gate (ATL-822). `spawnSession`
|
|
175
|
+
// registers a `directResolve` confirmation in `pendingInteractions` and then
|
|
176
|
+
// broadcasts a `confirmation_request`. Real broadcasting needs the event hub /
|
|
177
|
+
// SSE wiring, so the mock auto-resolves the freshly registered interaction
|
|
178
|
+
// the same way `POST /v1/confirm` would (resolve + directResolve). Tests flip
|
|
179
|
+
// `approvalBehavior` to exercise allow / deny, and read `confirmationRequests`
|
|
180
|
+
// to assert the prompt's risk shape. `interaction_resolved` and other event
|
|
181
|
+
// types are ignored.
|
|
182
|
+
type ApprovalBehavior = "allow" | "deny";
|
|
183
|
+
let approvalBehavior: ApprovalBehavior = "allow";
|
|
184
|
+
const confirmationRequests: Array<Record<string, unknown>> = [];
|
|
185
|
+
|
|
186
|
+
mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
187
|
+
broadcastMessage: (msg: { type?: string; requestId?: string }) => {
|
|
188
|
+
if (msg?.type !== "confirmation_request") return;
|
|
189
|
+
confirmationRequests.push(msg as Record<string, unknown>);
|
|
190
|
+
const decision = approvalBehavior;
|
|
191
|
+
const interaction = pendingInteractions.resolve(
|
|
192
|
+
msg.requestId as string,
|
|
193
|
+
decision === "allow" ? "approved" : "rejected",
|
|
194
|
+
);
|
|
195
|
+
interaction?.directResolve?.(decision);
|
|
196
|
+
},
|
|
197
|
+
}));
|
|
198
|
+
|
|
165
199
|
import { eq } from "drizzle-orm";
|
|
166
200
|
|
|
167
201
|
import { getDb, getSqlite } from "../../memory/db-connection.js";
|
|
@@ -185,6 +219,8 @@ beforeEach(() => {
|
|
|
185
219
|
capturedSpawns.length = 0;
|
|
186
220
|
vaultStore.clear();
|
|
187
221
|
metadataStore.clear();
|
|
222
|
+
approvalBehavior = "allow";
|
|
223
|
+
confirmationRequests.length = 0;
|
|
188
224
|
});
|
|
189
225
|
|
|
190
226
|
// ---------------------------------------------------------------------------
|
|
@@ -192,63 +228,108 @@ beforeEach(() => {
|
|
|
192
228
|
// ---------------------------------------------------------------------------
|
|
193
229
|
|
|
194
230
|
describe("POST /v1/acp/spawn", () => {
|
|
195
|
-
test("throws BadRequestError when
|
|
196
|
-
config.setConfig({
|
|
231
|
+
test("throws BadRequestError with merged available list when agent id is unknown", async () => {
|
|
232
|
+
config.setConfig({
|
|
233
|
+
agents: {
|
|
234
|
+
"user-only": { command: "some-binary", args: [] },
|
|
235
|
+
},
|
|
236
|
+
});
|
|
197
237
|
|
|
198
238
|
const handler = getSpawnHandler();
|
|
199
239
|
await expect(
|
|
200
240
|
handler({
|
|
201
241
|
body: {
|
|
202
|
-
agent: "
|
|
242
|
+
agent: "nonexistent",
|
|
203
243
|
task: "do a thing",
|
|
204
244
|
conversationId: "conv-1",
|
|
205
245
|
},
|
|
206
246
|
}),
|
|
207
|
-
).rejects.toThrow("
|
|
247
|
+
).rejects.toThrow('Unknown agent "nonexistent"');
|
|
208
248
|
});
|
|
209
249
|
|
|
210
|
-
test("
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
250
|
+
test("body-shape guard short-circuits before the resolver runs", async () => {
|
|
251
|
+
const handler = getSpawnHandler();
|
|
252
|
+
await expect(handler({ body: { agent: "claude" } })).rejects.toThrow(
|
|
253
|
+
"agent, task, and conversationId are required",
|
|
254
|
+
);
|
|
255
|
+
// Guard runs before the approval gate — no prompt is surfaced.
|
|
256
|
+
expect(confirmationRequests).toHaveLength(0);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// POST /v1/acp/spawn — high-risk approval gate (ATL-822)
|
|
262
|
+
//
|
|
263
|
+
// Spawning an ACP agent is a host subprocess with auto-allowed
|
|
264
|
+
// filesystem/terminal access. The skill-tool path gets the descriptor's
|
|
265
|
+
// `risk: "high"` prompt via ToolExecutor; this route reaches the session
|
|
266
|
+
// manager directly, so it surfaces the same confirmation itself and refuses
|
|
267
|
+
// to spawn unless a guardian approves.
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
describe("POST /v1/acp/spawn — approval gate", () => {
|
|
271
|
+
test("surfaces a high-risk host confirmation before spawning", async () => {
|
|
272
|
+
seedVaultToken("test-token-abc123");
|
|
273
|
+
|
|
274
|
+
const handler = getSpawnHandler();
|
|
275
|
+
await handler({
|
|
276
|
+
body: {
|
|
277
|
+
agent: "claude",
|
|
278
|
+
task: "do a thing",
|
|
279
|
+
conversationId: "conv-1",
|
|
280
|
+
cwd: "/work/repo",
|
|
214
281
|
},
|
|
215
282
|
});
|
|
216
283
|
|
|
284
|
+
expect(confirmationRequests).toHaveLength(1);
|
|
285
|
+
const prompt = confirmationRequests[0];
|
|
286
|
+
expect(prompt.toolName).toBe("acp_spawn");
|
|
287
|
+
expect(prompt.riskLevel).toBe("high");
|
|
288
|
+
expect(prompt.executionTarget).toBe("host");
|
|
289
|
+
expect(prompt.conversationId).toBe("conv-1");
|
|
290
|
+
expect((prompt.input as { cwd?: string }).cwd).toBe("/work/repo");
|
|
291
|
+
// Prompt resolved "allow" → spawn proceeds.
|
|
292
|
+
expect(capturedSpawns).toHaveLength(1);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("throws ForbiddenError and does not spawn when the guardian denies", async () => {
|
|
296
|
+
approvalBehavior = "deny";
|
|
297
|
+
seedVaultToken("test-token-abc123");
|
|
298
|
+
|
|
217
299
|
const handler = getSpawnHandler();
|
|
218
300
|
await expect(
|
|
219
301
|
handler({
|
|
220
302
|
body: {
|
|
221
|
-
agent: "
|
|
303
|
+
agent: "claude",
|
|
222
304
|
task: "do a thing",
|
|
223
305
|
conversationId: "conv-1",
|
|
224
306
|
},
|
|
225
307
|
}),
|
|
226
|
-
).rejects.toThrow(
|
|
308
|
+
).rejects.toThrow(/guardian approval/i);
|
|
309
|
+
|
|
310
|
+
// Denied before any host side effects: no subprocess, no pending leak.
|
|
311
|
+
expect(capturedSpawns).toHaveLength(0);
|
|
312
|
+
expect(pendingInteractions.getAll()).toHaveLength(0);
|
|
227
313
|
});
|
|
228
314
|
|
|
229
|
-
test("
|
|
230
|
-
|
|
231
|
-
|
|
315
|
+
test("denies (and does not spawn) when the client disconnects before approval", async () => {
|
|
316
|
+
seedVaultToken("test-token-abc123");
|
|
317
|
+
const controller = new AbortController();
|
|
318
|
+
controller.abort();
|
|
232
319
|
|
|
233
320
|
const handler = getSpawnHandler();
|
|
234
321
|
await expect(
|
|
235
322
|
handler({
|
|
236
323
|
body: {
|
|
237
|
-
agent: "
|
|
324
|
+
agent: "claude",
|
|
238
325
|
task: "do a thing",
|
|
239
326
|
conversationId: "conv-1",
|
|
240
327
|
},
|
|
328
|
+
abortSignal: controller.signal,
|
|
241
329
|
}),
|
|
242
|
-
).rejects.toThrow(
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test("body-shape guard short-circuits before the resolver runs", async () => {
|
|
246
|
-
config.setConfig({ enabled: false });
|
|
330
|
+
).rejects.toThrow(/guardian approval/i);
|
|
247
331
|
|
|
248
|
-
|
|
249
|
-
await expect(handler({ body: { agent: "claude" } })).rejects.toThrow(
|
|
250
|
-
"agent, task, and conversationId are required",
|
|
251
|
-
);
|
|
332
|
+
expect(capturedSpawns).toHaveLength(0);
|
|
252
333
|
});
|
|
253
334
|
});
|
|
254
335
|
|
|
@@ -457,6 +538,8 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
|
|
|
457
538
|
});
|
|
458
539
|
|
|
459
540
|
beforeEach(() => {
|
|
541
|
+
inMemoryStates.clear();
|
|
542
|
+
pendingResumeIds.clear();
|
|
460
543
|
getSqlite().run("DELETE FROM acp_session_history");
|
|
461
544
|
});
|
|
462
545
|
|
|
@@ -482,6 +565,57 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
|
|
|
482
565
|
]);
|
|
483
566
|
});
|
|
484
567
|
|
|
568
|
+
test("excludes terminal rows whose session is active in memory (resumed sessions)", async () => {
|
|
569
|
+
// A resumed session reuses its original id: the in-memory state is
|
|
570
|
+
// `running` while its history row still carries the old terminal
|
|
571
|
+
// status until the next terminal upsert. The bulk delete must not
|
|
572
|
+
// remove that row out from under the live session.
|
|
573
|
+
seedHistoryRow("row-resumed", "completed", 1000);
|
|
574
|
+
seedHistoryRow("row-completed", "completed", 2000);
|
|
575
|
+
inMemoryStates.set("row-resumed", {
|
|
576
|
+
id: "row-resumed",
|
|
577
|
+
agentId: "claude",
|
|
578
|
+
acpSessionId: "proto-resumed",
|
|
579
|
+
parentConversationId: "conv-test",
|
|
580
|
+
status: "running",
|
|
581
|
+
startedAt: 1000,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
const handler = getBulkDeleteHandler();
|
|
585
|
+
const result = (await handler({
|
|
586
|
+
queryParams: { status: "completed" },
|
|
587
|
+
})) as {
|
|
588
|
+
deleted: number;
|
|
589
|
+
};
|
|
590
|
+
expect(result.deleted).toBe(1);
|
|
591
|
+
|
|
592
|
+
const remaining = listRows();
|
|
593
|
+
expect(remaining).toEqual([{ id: "row-resumed", status: "completed" }]);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
test("excludes terminal rows whose session has a resume in flight", async () => {
|
|
597
|
+
// A resume that is still awaiting env preparation has no in-memory
|
|
598
|
+
// SessionEntry yet, but its history row (still terminal) must survive:
|
|
599
|
+
// deleting it mid-resume would let the later terminal upsert resurrect
|
|
600
|
+
// it as an orphan row.
|
|
601
|
+
seedHistoryRow("row-pending-resume", "completed", 1000);
|
|
602
|
+
seedHistoryRow("row-completed", "completed", 2000);
|
|
603
|
+
pendingResumeIds.add("row-pending-resume");
|
|
604
|
+
|
|
605
|
+
const handler = getBulkDeleteHandler();
|
|
606
|
+
const result = (await handler({
|
|
607
|
+
queryParams: { status: "completed" },
|
|
608
|
+
})) as {
|
|
609
|
+
deleted: number;
|
|
610
|
+
};
|
|
611
|
+
expect(result.deleted).toBe(1);
|
|
612
|
+
|
|
613
|
+
const remaining = listRows();
|
|
614
|
+
expect(remaining).toEqual([
|
|
615
|
+
{ id: "row-pending-resume", status: "completed" },
|
|
616
|
+
]);
|
|
617
|
+
});
|
|
618
|
+
|
|
485
619
|
test("returns deleted=0 when no terminal rows are present", async () => {
|
|
486
620
|
seedHistoryRow("row-running", "running", 1000);
|
|
487
621
|
|
|
@@ -559,6 +693,7 @@ describe("DELETE /v1/acp/sessions/:id", () => {
|
|
|
559
693
|
|
|
560
694
|
beforeEach(() => {
|
|
561
695
|
inMemoryStates.clear();
|
|
696
|
+
pendingResumeIds.clear();
|
|
562
697
|
getDb().delete(acpSessionHistory).run();
|
|
563
698
|
});
|
|
564
699
|
|
|
@@ -610,6 +745,24 @@ describe("DELETE /v1/acp/sessions/:id", () => {
|
|
|
610
745
|
},
|
|
611
746
|
);
|
|
612
747
|
|
|
748
|
+
test("returns 409 when the session has a resume in flight (not yet in memory)", async () => {
|
|
749
|
+
pendingResumeIds.add("sess-resuming");
|
|
750
|
+
insertHistoryRow({ id: "sess-resuming", status: "completed" });
|
|
751
|
+
|
|
752
|
+
const handler = getDeleteSessionHandler();
|
|
753
|
+
expect(() => handler({ pathParams: { id: "sess-resuming" } })).toThrow(
|
|
754
|
+
"resume in flight",
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
// Row untouched.
|
|
758
|
+
const remaining = getDb()
|
|
759
|
+
.select()
|
|
760
|
+
.from(acpSessionHistory)
|
|
761
|
+
.where(eq(acpSessionHistory.id, "sess-resuming"))
|
|
762
|
+
.all();
|
|
763
|
+
expect(remaining).toHaveLength(1);
|
|
764
|
+
});
|
|
765
|
+
|
|
613
766
|
test("idempotent for unknown id — returns { deleted: false }", async () => {
|
|
614
767
|
const handler = getDeleteSessionHandler();
|
|
615
768
|
const result = (await handler({
|