@vellumai/assistant 0.8.7 → 0.8.8-dev.202606052332.17fc8ea
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/Dockerfile +20 -4
- package/bun.lock +2 -2
- package/docker-entrypoint.sh +4 -2
- package/docker-init-apt-root.sh +3 -1
- package/docker-kata-apt-env.sh +3 -1
- package/docker-kata-runtime-family.sh +12 -0
- package/docs/architecture/memory.md +1 -1
- package/examples/plugins/echo/README.md +61 -66
- 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/server-message.ts +3 -3
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
- package/openapi.yaml +3378 -335
- package/package.json +2 -2
- package/scripts/generate-openapi.ts +68 -41
- package/src/__tests__/agent-loop-exit-reason.test.ts +35 -93
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +37 -87
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -3
- package/src/__tests__/anthropic-provider.test.ts +95 -2
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +4 -1
- package/src/__tests__/assistant-event-hub.test.ts +25 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
- package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
- package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
- package/src/__tests__/btw-routes.test.ts +62 -3
- package/src/__tests__/build-persisted-content.test.ts +184 -0
- package/src/__tests__/catalog-files.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__/clawhub-files.test.ts +1 -1
- package/src/__tests__/compaction-circuit.test.ts +258 -0
- package/src/__tests__/compaction-direct.test.ts +132 -0
- package/src/__tests__/compaction.benchmark.test.ts +0 -30
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -5
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -7
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +316 -1143
- package/src/__tests__/conversation-agent-loop.test.ts +638 -1655
- package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-history-web-search.test.ts +11 -1
- package/src/__tests__/conversation-pairing.test.ts +4 -31
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -10
- package/src/__tests__/conversation-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
- package/src/__tests__/conversation-runtime-assembly.test.ts +310 -300
- package/src/__tests__/conversation-runtime-workspace.test.ts +105 -45
- package/src/__tests__/conversation-slash-commands.test.ts +8 -42
- package/src/__tests__/conversation-slash-queue.test.ts +6 -1
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
- package/src/__tests__/conversation-sync-tags.test.ts +27 -15
- package/src/__tests__/conversation-title-service.test.ts +135 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +17 -16
- package/src/__tests__/conversation-workspace-injection.test.ts +67 -2
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +7 -6
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/cross-provider-web-search.test.ts +214 -1
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
- package/src/__tests__/dm-persistence.test.ts +5 -1
- package/src/__tests__/dynamic-page-surface.test.ts +31 -0
- package/src/__tests__/empty-response-hook.test.ts +304 -0
- package/src/__tests__/feature-flag-test-helpers.ts +2 -2
- package/src/__tests__/file-write-tool.test.ts +63 -0
- package/src/__tests__/gateway-only-guard.test.ts +12 -2
- package/src/__tests__/gemini-image-service.test.ts +13 -0
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/helpers/mock-provider.ts +110 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
- package/src/__tests__/history-repair-hook.test.ts +1 -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__/identity-intro-cache.test.ts +12 -100
- package/src/__tests__/identity-routes.test.ts +248 -7
- package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
- package/src/__tests__/injector-background-turn.test.ts +3 -9
- package/src/__tests__/injector-chain.test.ts +139 -275
- package/src/__tests__/injector-disk-pressure.test.ts +75 -41
- package/src/__tests__/injector-document-comments.test.ts +3 -3
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
- package/src/__tests__/injector-v3-suppression.test.ts +31 -37
- package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
- package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
- package/src/__tests__/list-messages-page-latest.test.ts +60 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
- package/src/__tests__/llm-usage-store.test.ts +223 -1
- package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
- package/src/__tests__/native-web-search.test.ts +191 -0
- package/src/__tests__/onboarding-template-contract.test.ts +2 -0
- package/src/__tests__/openai-image-service.test.ts +17 -0
- package/src/__tests__/openai-provider.test.ts +31 -1
- package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -284
- package/src/__tests__/persist-unsendable-image.test.ts +215 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
- package/src/__tests__/pkb-autoinject.test.ts +2 -5
- package/src/__tests__/plugin-api-shim.test.ts +3 -6
- package/src/__tests__/plugin-bootstrap.test.ts +14 -40
- package/src/__tests__/plugin-registry.test.ts +3 -76
- package/src/__tests__/plugin-types.test.ts +0 -193
- package/src/__tests__/process-message-display-content.test.ts +6 -2
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
- package/src/__tests__/resolve-trust-class.test.ts +4 -4
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
- package/src/__tests__/schedule-routes.test.ts +603 -2
- package/src/__tests__/schedule-store.test.ts +41 -0
- package/src/__tests__/schedule-tools.test.ts +35 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/server-history-render.test.ts +314 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
- package/src/__tests__/skillssh-files.test.ts +1 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- 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__/system-prompt.test.ts +20 -0
- package/src/__tests__/task-scheduler.test.ts +162 -1
- package/src/__tests__/terminal-tools.test.ts +6 -1
- package/src/__tests__/title-generate-hook.test.ts +319 -0
- package/src/__tests__/tool-error-hook.test.ts +278 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -2
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
- package/src/__tests__/ui-work-result-surface.test.ts +159 -0
- package/src/__tests__/usage-routes.test.ts +285 -1
- package/src/__tests__/user-plugin-loader.test.ts +54 -286
- package/src/__tests__/voice-session-bridge.test.ts +6 -3
- package/src/__tests__/web-search-backend-failure.test.ts +166 -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-history-db.ts +82 -0
- package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +196 -0
- package/src/acp/auto-install.ts +177 -0
- package/src/acp/client-handler.ts +31 -0
- package/src/acp/feature-gate.test.ts +48 -0
- package/src/acp/feature-gate.ts +34 -0
- package/src/acp/prepare-agent-env.ts +83 -29
- package/src/acp/resolve-agent.test.ts +320 -7
- package/src/acp/resolve-agent.ts +182 -18
- package/src/acp/resume-hint.ts +25 -0
- package/src/acp/session-manager.ts +495 -73
- package/src/acp/types.ts +8 -0
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +362 -485
- package/src/api/events/assistant-thinking-delta.ts +33 -0
- package/src/api/events/tool-output-chunk.ts +45 -0
- package/src/api/events/tool-use-preview-start.ts +32 -0
- package/src/api/events/trace-event.ts +69 -0
- package/src/api/index.ts +48 -13
- package/src/api/responses/conversation-message.ts +374 -0
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/avatar/__tests__/avatar-store.test.ts +34 -29
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/__tests__/notifications.test.ts +58 -14
- package/src/cli/commands/notifications.ts +112 -60
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +10 -0
- package/src/config/acp-defaults.ts +6 -0
- package/src/config/assistant-feature-flags.ts +22 -11
- package/src/config/bundled-skills/acp/SKILL.md +83 -31
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +224 -398
- package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
- package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
- package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
- package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
- package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +0 -7
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/feature-flag-cache.ts +3 -3
- package/src/config/feature-flag-registry.json +48 -7
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-v2.ts +8 -0
- package/src/config/schemas/memory-v3.ts +8 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills.ts +13 -0
- package/src/context/compactor.ts +1 -1
- package/src/context/strip-injections.ts +128 -0
- package/src/context/token-estimator.ts +23 -0
- package/src/context/tool-result-truncation.ts +0 -23
- package/src/context/window-manager.ts +5 -7
- package/src/credential-execution/executable-discovery.ts +16 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
- package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/config-watcher.ts +2 -2
- package/src/daemon/context-overflow-reducer.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +594 -153
- package/src/daemon/conversation-agent-loop.ts +301 -997
- package/src/daemon/conversation-history.ts +5 -4
- package/src/daemon/conversation-lifecycle.ts +3 -4
- package/src/daemon/conversation-messaging.ts +7 -6
- package/src/daemon/conversation-process.ts +11 -16
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +218 -398
- package/src/daemon/conversation-slash.ts +6 -25
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +222 -4
- package/src/daemon/conversation-tool-setup.ts +2 -29
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +32 -20
- package/src/daemon/external-plugins-bootstrap.ts +17 -18
- package/src/daemon/handlers/config-a2a.ts +51 -36
- package/src/daemon/handlers/config-slack-channel.ts +20 -14
- package/src/daemon/handlers/config-telegram.ts +16 -2
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/shared.ts +156 -84
- package/src/daemon/handlers/skills.ts +42 -10
- package/src/daemon/lifecycle.ts +25 -0
- package/src/daemon/message-types/apps.ts +1 -29
- package/src/daemon/message-types/messages.ts +9 -57
- package/src/daemon/message-types/skills.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +136 -3
- package/src/daemon/now-scratchpad.ts +21 -0
- package/src/daemon/orphan-reaper.test.ts +210 -0
- package/src/daemon/orphan-reaper.ts +240 -0
- package/src/daemon/overflow-reduction-loop.ts +230 -0
- package/src/daemon/persist-unsendable-image.ts +117 -0
- package/src/daemon/process-message.ts +1 -3
- package/src/daemon/server.ts +2 -0
- package/src/daemon/trace-emitter.ts +6 -4
- package/src/daemon/trust-context.ts +19 -0
- package/src/daemon/wake-target-adapter.ts +3 -1
- 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/home/home-greeting-cache.ts +24 -1
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/gateway-client.test.ts +2 -2
- package/src/ipc/gateway-client.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/media/gemini-image-service.ts +15 -0
- package/src/media/openai-image-service.ts +14 -0
- package/src/media/types.ts +34 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
- package/src/memory/auth-fallback-events-store.ts +94 -0
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/conversation-title-service.ts +65 -41
- package/src/memory/db-init.ts +6 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
- package/src/memory/graph/conversation-graph-memory.ts +65 -0
- package/src/memory/job-handlers/conversation-starters.ts +13 -2
- package/src/memory/jobs-store.ts +33 -0
- package/src/memory/jobs-worker.ts +32 -5
- package/src/memory/llm-usage-store.ts +224 -50
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
- package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
- package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
- package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/pkb/autoinject.ts +61 -0
- package/src/memory/pkb/context.ts +50 -0
- package/src/memory/pkb/types.ts +14 -0
- package/src/memory/schedule-attribution-sql.ts +104 -0
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/schema/infrastructure.ts +16 -0
- package/src/memory/usage-grouped-buckets.ts +6 -1
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -4
- package/src/memory/v2/consolidation-job.ts +14 -5
- package/src/notifications/conversation-pairing.ts +8 -15
- package/src/notifications/decision-engine.ts +6 -3
- package/src/notifications/home-feed-side-effect.ts +12 -1
- package/src/permissions/prompter.ts +4 -0
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +7 -5
- package/src/plugin-api/types.ts +151 -1
- package/src/plugins/defaults/compaction/compact.ts +59 -0
- package/src/plugins/defaults/compaction/package.json +1 -1
- package/src/plugins/defaults/compaction/register.ts +8 -19
- package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
- package/src/plugins/defaults/empty-response/register.ts +8 -13
- package/src/plugins/defaults/index.ts +2 -18
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
- package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +288 -81
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +16 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +48 -12
- package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +104 -32
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +96 -30
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +34 -16
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -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 +75 -0
- package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +8 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +26 -14
- package/src/plugins/defaults/{llm-call → memory-v3-shadow}/package.json +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +3 -3
- package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +51 -45
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +61 -46
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +69 -99
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +8 -0
- package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
- package/src/plugins/defaults/title-generate/package.json +1 -1
- package/src/plugins/defaults/title-generate/register.ts +18 -18
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
- package/src/plugins/defaults/tool-error/package.json +1 -1
- package/src/plugins/defaults/tool-error/register.ts +9 -21
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
- package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
- package/src/plugins/external-api.ts +2 -2
- package/src/plugins/pipeline.ts +6 -305
- package/src/plugins/registry.ts +10 -55
- package/src/plugins/types.ts +62 -797
- package/src/plugins/user-loader.ts +30 -127
- package/src/proactive-artifact/aux-message-injector.ts +4 -4
- package/src/proactive-artifact/job.test.ts +8 -13
- package/src/prompts/__tests__/system-prompt.test.ts +42 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +15 -0
- package/src/providers/anthropic/client.ts +37 -29
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -0
- package/src/providers/openrouter/client.ts +1 -0
- package/src/providers/placeholder-sentinels.ts +35 -0
- package/src/runtime/__tests__/agent-wake.test.ts +10 -6
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +2 -5
- package/src/runtime/assistant-event-hub.ts +37 -7
- package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/http-router.ts +16 -21
- package/src/runtime/http-types.ts +16 -70
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
- 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/__tests__/tts-routes.test.ts +6 -2
- package/src/runtime/routes/acp-routes.test.ts +89 -25
- package/src/runtime/routes/acp-routes.ts +81 -29
- package/src/runtime/routes/app-management-routes.ts +6 -117
- package/src/runtime/routes/app-routes.ts +13 -15
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/attachment-routes.ts +26 -15
- package/src/runtime/routes/avatar-routes.ts +26 -0
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +6 -10
- package/src/runtime/routes/btw-routes.ts +29 -23
- package/src/runtime/routes/consolidation-routes.ts +120 -20
- 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 +3 -1
- package/src/runtime/routes/conversation-routes.ts +372 -185
- package/src/runtime/routes/conversation-starter-routes.ts +13 -7
- package/src/runtime/routes/conversations-import-routes.ts +24 -7
- package/src/runtime/routes/documents-routes.ts +4 -0
- package/src/runtime/routes/domain-routes.ts +51 -37
- package/src/runtime/routes/epoch-millis-range.ts +34 -0
- package/src/runtime/routes/events-routes.ts +28 -34
- package/src/runtime/routes/gateway-log-routes.ts +26 -4
- package/src/runtime/routes/heartbeat-routes.ts +32 -12
- 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-intro-cache.ts +11 -34
- package/src/runtime/routes/identity-routes.ts +224 -18
- package/src/runtime/routes/image-generation-routes.ts +40 -2
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/a2a.ts +12 -10
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
- package/src/runtime/routes/integrations/slack/channel.ts +4 -0
- package/src/runtime/routes/integrations/slack/share.ts +27 -6
- package/src/runtime/routes/integrations/telegram.ts +6 -0
- package/src/runtime/routes/integrations/twilio.ts +42 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
- package/src/runtime/routes/log-export-routes.ts +8 -0
- package/src/runtime/routes/memory-v2-routes.ts +15 -8
- package/src/runtime/routes/memory-v3-routes.ts +66 -34
- package/src/runtime/routes/oauth-apps.ts +66 -12
- package/src/runtime/routes/oauth-providers.ts +44 -5
- package/src/runtime/routes/platform-routes.ts +81 -5
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
- package/src/runtime/routes/playground/force-compact.ts +1 -1
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/rename-conversation-routes.ts +5 -0
- package/src/runtime/routes/schedule-routes.ts +152 -42
- package/src/runtime/routes/secret-routes.ts +14 -2
- package/src/runtime/routes/skills-routes.ts +43 -14
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
- package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
- package/src/runtime/routes/trust-rules-routes.ts +26 -2
- package/src/runtime/routes/tts-routes.ts +35 -0
- package/src/runtime/routes/types.ts +66 -8
- package/src/runtime/routes/usage-routes.ts +47 -39
- package/src/runtime/routes/webhook-routes.ts +41 -2
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/routes/workspace-routes.ts +4 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
- package/src/runtime/services/analyze-conversation.ts +2 -2
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/schedule/schedule-store.ts +20 -1
- package/src/schedule/schedule-usage-store.ts +83 -0
- package/src/schedule/scheduler.ts +12 -5
- package/src/signals/cancel.ts +2 -4
- package/src/skills/catalog-files.ts +2 -2
- package/src/skills/catalog-install.ts +3 -0
- package/src/skills/categories-cache.ts +118 -0
- package/src/skills/clawhub-files.ts +1 -2
- package/src/skills/skillssh-files.ts +1 -2
- package/src/subagent/manager.ts +17 -5
- package/src/telemetry/types.ts +29 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
- package/src/telemetry/usage-telemetry-reporter.ts +57 -2
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +7 -1
- package/src/tools/acp/spawn.test.ts +158 -55
- package/src/tools/acp/spawn.ts +47 -72
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +13 -8
- package/src/tools/executor.ts +1 -53
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
- package/src/tools/network/__tests__/web-search.test.ts +11 -3
- package/src/tools/network/web-search-error.test.ts +248 -0
- package/src/tools/network/web-search-error.ts +267 -0
- package/src/tools/network/web-search.ts +207 -48
- package/src/tools/schedule/create.ts +2 -0
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/terminal/safe-env.ts +10 -1
- package/src/tools/ui-surface/definitions.ts +34 -5
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
- package/src/tts/provider-catalog.ts +76 -1
- package/src/util/mutex.ts +47 -0
- package/src/workspace/git-service.ts +1 -42
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
- package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
- package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/docs/plugins.md +0 -836
- package/examples/plugins/echo/register.ts +0 -184
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
- 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__/empty-response-pipeline.test.ts +0 -423
- package/src/__tests__/llm-call-pipeline.test.ts +0 -287
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
- package/src/__tests__/persistence-pipeline.test.ts +0 -503
- package/src/__tests__/pipeline-runner.test.ts +0 -564
- package/src/__tests__/title-generate-pipeline.test.ts +0 -211
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
- package/src/__tests__/tool-error-pipeline.test.ts +0 -241
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
- package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
- package/src/gallery/default-gallery.ts +0 -1359
- package/src/gallery/gallery-manifest.ts +0 -28
- package/src/home/feature-gate.ts +0 -22
- package/src/memory/v3/provider-blocks.ts +0 -16
- 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/terminal.ts +0 -73
- package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
- package/src/plugins/defaults/empty-response/terminal.ts +0 -106
- package/src/plugins/defaults/injectors/package.json +0 -15
- package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
- package/src/plugins/defaults/llm-call/register.ts +0 -45
- package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
- package/src/plugins/defaults/memory-retrieval/package.json +0 -15
- package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
- 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/persistence/middlewares/persistence.ts +0 -19
- package/src/plugins/defaults/persistence/package.json +0 -15
- package/src/plugins/defaults/persistence/register.ts +0 -38
- package/src/plugins/defaults/persistence/terminal.ts +0 -83
- package/src/plugins/defaults/title-generate/terminal.ts +0 -31
- package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
- package/src/plugins/defaults/token-estimate/package.json +0 -15
- package/src/plugins/defaults/token-estimate/register.ts +0 -34
- package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
- package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
- package/src/plugins/defaults/tool-error/terminal.ts +0 -47
- package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
- package/src/plugins/defaults/tool-execute/package.json +0 -15
- package/src/plugins/defaults/tool-execute/register.ts +0 -49
- package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
- package/src/skills/category-inference.ts +0 -111
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the `toolError` pipeline (PR 19).
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* - Default plugin nudges on the first error turn and keeps nudging up to the
|
|
6
|
-
* `maxConsecutiveErrorNudges` cap.
|
|
7
|
-
* - Default plugin suppresses the nudge once the cap is exceeded (the error is
|
|
8
|
-
* likely unrecoverable — burning tokens on more nudges is wasteful).
|
|
9
|
-
* - Default plugin uses the canonical {@link DEFAULT_TOOL_ERROR_NUDGE_TEXT}.
|
|
10
|
-
* - Default plugin skips when `hasToolError` is false, regardless of the
|
|
11
|
-
* consecutive counter (no error this turn → nothing to nudge).
|
|
12
|
-
* - Swapping in a user plugin that provides its own `toolError` middleware
|
|
13
|
-
* changes the nudge text end-to-end through `runPipeline`.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { beforeEach, describe, expect, test } from "bun:test";
|
|
17
|
-
|
|
18
|
-
import type { TrustContext } from "../daemon/trust-context.js";
|
|
19
|
-
import { defaultToolErrorPlugin } from "../plugins/defaults/tool-error/register.js";
|
|
20
|
-
import {
|
|
21
|
-
DEFAULT_TOOL_ERROR_NUDGE_TEXT,
|
|
22
|
-
defaultToolErrorTerminal,
|
|
23
|
-
} from "../plugins/defaults/tool-error/terminal.js";
|
|
24
|
-
import { runPipeline } from "../plugins/pipeline.js";
|
|
25
|
-
import {
|
|
26
|
-
getMiddlewaresFor,
|
|
27
|
-
registerPlugin,
|
|
28
|
-
resetPluginRegistryForTests,
|
|
29
|
-
} from "../plugins/registry.js";
|
|
30
|
-
import {
|
|
31
|
-
type Middleware,
|
|
32
|
-
type Plugin,
|
|
33
|
-
type ToolErrorArgs,
|
|
34
|
-
type ToolErrorDecision,
|
|
35
|
-
type TurnContext,
|
|
36
|
-
} from "../plugins/types.js";
|
|
37
|
-
|
|
38
|
-
const trust: TrustContext = {
|
|
39
|
-
sourceChannel: "vellum",
|
|
40
|
-
trustClass: "guardian",
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
function makeCtx(): TurnContext {
|
|
44
|
-
return {
|
|
45
|
-
requestId: "req-tool-error-test",
|
|
46
|
-
conversationId: "conv-tool-error-test",
|
|
47
|
-
turnIndex: 0,
|
|
48
|
-
trust,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function runToolErrorPipeline(
|
|
53
|
-
args: ToolErrorArgs,
|
|
54
|
-
): Promise<ToolErrorDecision> {
|
|
55
|
-
// Mirror the production call site in `agent/loop.ts`: the pipeline terminal
|
|
56
|
-
// is `defaultToolErrorTerminal`, not a no-op. The default plugin's
|
|
57
|
-
// middleware is a passthrough that calls `next(args)`, so the decision
|
|
58
|
-
// logic lives in the terminal.
|
|
59
|
-
return runPipeline<ToolErrorArgs, ToolErrorDecision>(
|
|
60
|
-
"toolError",
|
|
61
|
-
getMiddlewaresFor("toolError"),
|
|
62
|
-
async (pipelineArgs) => defaultToolErrorTerminal(pipelineArgs),
|
|
63
|
-
args,
|
|
64
|
-
makeCtx(),
|
|
65
|
-
500,
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
describe("toolError pipeline", () => {
|
|
70
|
-
describe("default plugin", () => {
|
|
71
|
-
beforeEach(() => {
|
|
72
|
-
resetPluginRegistryForTests();
|
|
73
|
-
registerPlugin(defaultToolErrorPlugin);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("nudges on first error turn with canonical text", async () => {
|
|
77
|
-
const decision = await runToolErrorPipeline({
|
|
78
|
-
hasToolError: true,
|
|
79
|
-
consecutiveErrorTurns: 1,
|
|
80
|
-
maxConsecutiveErrorNudges: 3,
|
|
81
|
-
});
|
|
82
|
-
expect(decision.action).toBe("nudge");
|
|
83
|
-
if (decision.action === "nudge") {
|
|
84
|
-
expect(decision.nudgeText).toBe(DEFAULT_TOOL_ERROR_NUDGE_TEXT);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("keeps nudging up to and including the cap", async () => {
|
|
89
|
-
// Cap of 3: turns 1, 2, and 3 all nudge. Turn 4 is past the cap.
|
|
90
|
-
for (let turn = 1; turn <= 3; turn++) {
|
|
91
|
-
const decision = await runToolErrorPipeline({
|
|
92
|
-
hasToolError: true,
|
|
93
|
-
consecutiveErrorTurns: turn,
|
|
94
|
-
maxConsecutiveErrorNudges: 3,
|
|
95
|
-
});
|
|
96
|
-
expect(decision.action).toBe("nudge");
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("suppresses the nudge once the consecutive counter exceeds the cap", async () => {
|
|
101
|
-
const decision = await runToolErrorPipeline({
|
|
102
|
-
hasToolError: true,
|
|
103
|
-
consecutiveErrorTurns: 4,
|
|
104
|
-
maxConsecutiveErrorNudges: 3,
|
|
105
|
-
});
|
|
106
|
-
expect(decision.action).toBe("skip");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("skips when there is no tool error this turn, regardless of counter", async () => {
|
|
110
|
-
// Counter is non-zero (the previous turn errored) but this turn succeeded,
|
|
111
|
-
// so nothing to nudge about.
|
|
112
|
-
const decision = await runToolErrorPipeline({
|
|
113
|
-
hasToolError: false,
|
|
114
|
-
consecutiveErrorTurns: 2,
|
|
115
|
-
maxConsecutiveErrorNudges: 3,
|
|
116
|
-
});
|
|
117
|
-
expect(decision.action).toBe("skip");
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("honors a caller-supplied cap of zero (never nudges)", async () => {
|
|
121
|
-
// Some call-sites may want to disable nudging entirely by passing cap = 0.
|
|
122
|
-
// The decision logic uses `<=`, so counter 0 with cap 0 does nudge; counter
|
|
123
|
-
// 1 with cap 0 suppresses. The cap is inclusive.
|
|
124
|
-
const turn1 = await runToolErrorPipeline({
|
|
125
|
-
hasToolError: true,
|
|
126
|
-
consecutiveErrorTurns: 1,
|
|
127
|
-
maxConsecutiveErrorNudges: 0,
|
|
128
|
-
});
|
|
129
|
-
expect(turn1.action).toBe("skip");
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe("user-supplied plugin", () => {
|
|
134
|
-
beforeEach(() => {
|
|
135
|
-
resetPluginRegistryForTests();
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("swapping in a plugin changes the nudge text", async () => {
|
|
139
|
-
const customText = "<system_notice>Custom error hint.</system_notice>";
|
|
140
|
-
const customMiddleware: Middleware<
|
|
141
|
-
ToolErrorArgs,
|
|
142
|
-
ToolErrorDecision
|
|
143
|
-
> = async (args) => {
|
|
144
|
-
if (args.hasToolError) {
|
|
145
|
-
return { action: "nudge", nudgeText: customText };
|
|
146
|
-
}
|
|
147
|
-
return { action: "skip" };
|
|
148
|
-
};
|
|
149
|
-
const customPlugin: Plugin = {
|
|
150
|
-
manifest: {
|
|
151
|
-
name: "custom-tool-error",
|
|
152
|
-
version: "0.0.1",
|
|
153
|
-
},
|
|
154
|
-
middleware: { toolError: customMiddleware },
|
|
155
|
-
};
|
|
156
|
-
registerPlugin(customPlugin);
|
|
157
|
-
|
|
158
|
-
const decision = await runToolErrorPipeline({
|
|
159
|
-
hasToolError: true,
|
|
160
|
-
consecutiveErrorTurns: 1,
|
|
161
|
-
maxConsecutiveErrorNudges: 3,
|
|
162
|
-
});
|
|
163
|
-
expect(decision.action).toBe("nudge");
|
|
164
|
-
if (decision.action === "nudge") {
|
|
165
|
-
expect(decision.nudgeText).toBe(customText);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("swapping in a plugin can suppress nudges even when the default would nudge", async () => {
|
|
170
|
-
const suppressingMiddleware: Middleware<
|
|
171
|
-
ToolErrorArgs,
|
|
172
|
-
ToolErrorDecision
|
|
173
|
-
> = async () => ({ action: "skip" });
|
|
174
|
-
const plugin: Plugin = {
|
|
175
|
-
manifest: {
|
|
176
|
-
name: "no-nudge",
|
|
177
|
-
version: "0.0.1",
|
|
178
|
-
},
|
|
179
|
-
middleware: { toolError: suppressingMiddleware },
|
|
180
|
-
};
|
|
181
|
-
registerPlugin(plugin);
|
|
182
|
-
|
|
183
|
-
const decision = await runToolErrorPipeline({
|
|
184
|
-
hasToolError: true,
|
|
185
|
-
consecutiveErrorTurns: 1,
|
|
186
|
-
maxConsecutiveErrorNudges: 3,
|
|
187
|
-
});
|
|
188
|
-
expect(decision.action).toBe("skip");
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test("terminal produces the nudge when no plugin is registered", async () => {
|
|
192
|
-
// No registerPlugin call — the registry is empty for this slot. The
|
|
193
|
-
// pipeline terminal is `defaultToolErrorTerminal`, so direct AgentLoop
|
|
194
|
-
// callers that skip `bootstrapPlugins()` still get the nudge even
|
|
195
|
-
// without any registered middleware.
|
|
196
|
-
const decision = await runToolErrorPipeline({
|
|
197
|
-
hasToolError: true,
|
|
198
|
-
consecutiveErrorTurns: 1,
|
|
199
|
-
maxConsecutiveErrorNudges: 3,
|
|
200
|
-
});
|
|
201
|
-
expect(decision.action).toBe("nudge");
|
|
202
|
-
if (decision.action === "nudge") {
|
|
203
|
-
expect(decision.nudgeText).toBe(DEFAULT_TOOL_ERROR_NUDGE_TEXT);
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test("user plugin registered AFTER the default still runs (no shadowing)", async () => {
|
|
208
|
-
// Production registration order: defaults load first via the side-effect
|
|
209
|
-
// imports in `defaults/index.ts`, then user plugins register on top via
|
|
210
|
-
// `bootstrapPlugins()`. The user's middleware ends up at a deeper onion
|
|
211
|
-
// layer than the default. If the default's middleware were to bypass
|
|
212
|
-
// `next` and call the decision logic directly, the user middleware
|
|
213
|
-
// would never run — this test guards against that regression.
|
|
214
|
-
registerPlugin(defaultToolErrorPlugin);
|
|
215
|
-
|
|
216
|
-
let userMiddlewareRan = false;
|
|
217
|
-
const userMiddleware: Middleware<
|
|
218
|
-
ToolErrorArgs,
|
|
219
|
-
ToolErrorDecision
|
|
220
|
-
> = async (args, next) => {
|
|
221
|
-
userMiddlewareRan = true;
|
|
222
|
-
return next(args);
|
|
223
|
-
};
|
|
224
|
-
registerPlugin({
|
|
225
|
-
manifest: {
|
|
226
|
-
name: "late-user-plugin",
|
|
227
|
-
version: "0.0.1",
|
|
228
|
-
},
|
|
229
|
-
middleware: { toolError: userMiddleware },
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
await runToolErrorPipeline({
|
|
233
|
-
hasToolError: true,
|
|
234
|
-
consecutiveErrorTurns: 1,
|
|
235
|
-
maxConsecutiveErrorNudges: 3,
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
expect(userMiddlewareRan).toBe(true);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
});
|
|
@@ -1,417 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pipeline wrapper tests for `ToolExecutor.execute` (PR 16).
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* - The public `execute` method routes through `runPipeline("toolExecute",
|
|
6
|
-
* ...)` so `getMiddlewaresFor("toolExecute")` middleware participates.
|
|
7
|
-
* - The default `toolExecute` plugin (passthrough) preserves the original
|
|
8
|
-
* execution path — result and side effects match the unwrapped executor.
|
|
9
|
-
* - A spy middleware observes the full tool invocation (name, input, ctx).
|
|
10
|
-
* - A short-circuit middleware intercepts the call and supplies a custom
|
|
11
|
-
* result without hitting the real tool.
|
|
12
|
-
*
|
|
13
|
-
* These tests reuse the same module mocks as `tool-executor.test.ts` so the
|
|
14
|
-
* permission check, risk classifier, and tool registry are stubbed; the
|
|
15
|
-
* focus here is the pipeline wrapper, not the internal execution body.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
19
|
-
|
|
20
|
-
import type { ToolExecutionResult } from "../tools/types.js";
|
|
21
|
-
|
|
22
|
-
// ── Config mock ───────────────────────────────────────────────────────
|
|
23
|
-
const mockConfig = {
|
|
24
|
-
provider: "anthropic",
|
|
25
|
-
model: "test",
|
|
26
|
-
maxTokens: 4096,
|
|
27
|
-
dataDir: "/tmp",
|
|
28
|
-
timeouts: {
|
|
29
|
-
shellDefaultTimeoutSec: 120,
|
|
30
|
-
shellMaxTimeoutSec: 600,
|
|
31
|
-
permissionTimeoutSec: 300,
|
|
32
|
-
toolExecutionTimeoutSec: 120,
|
|
33
|
-
},
|
|
34
|
-
sandbox: {
|
|
35
|
-
enabled: false,
|
|
36
|
-
backend: "native" as const,
|
|
37
|
-
docker: {
|
|
38
|
-
image: "vellum-sandbox:latest",
|
|
39
|
-
cpus: 1,
|
|
40
|
-
memoryMb: 512,
|
|
41
|
-
pidsLimit: 256,
|
|
42
|
-
network: "none" as const,
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
rateLimit: { maxRequestsPerMinute: 0 },
|
|
46
|
-
secretDetection: {
|
|
47
|
-
enabled: false,
|
|
48
|
-
},
|
|
49
|
-
permissions: {
|
|
50
|
-
mode: "workspace" as const,
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
mock.module("../config/loader.js", () => ({
|
|
55
|
-
getConfig: () => mockConfig,
|
|
56
|
-
loadConfig: () => mockConfig,
|
|
57
|
-
invalidateConfigCache: () => {},
|
|
58
|
-
loadRawConfig: () => ({}),
|
|
59
|
-
saveRawConfig: () => {},
|
|
60
|
-
getNestedValue: () => undefined,
|
|
61
|
-
setNestedValue: () => {},
|
|
62
|
-
}));
|
|
63
|
-
|
|
64
|
-
// ── Logger mock ──────────────────────────────────────────────────────
|
|
65
|
-
// Bun's `mock.module` persists across test files until explicitly
|
|
66
|
-
// restored, so this mock can leak into `plugin-bootstrap.test.ts`. That
|
|
67
|
-
// file inspects `ctx.logger` (populated via `log.child({ plugin })`), so
|
|
68
|
-
// we return a Proxy whose `.child(...)` yields another Proxy with the
|
|
69
|
-
// same shape — the bootstrap test's `expect(ctx.logger).toBeDefined()`
|
|
70
|
-
// then passes regardless of test-file ordering.
|
|
71
|
-
function makeFakeLoggerProxy(): object {
|
|
72
|
-
return new Proxy({} as Record<string, unknown>, {
|
|
73
|
-
get: (_target, prop) => {
|
|
74
|
-
if (prop === "child") return () => makeFakeLoggerProxy();
|
|
75
|
-
return () => {};
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
mock.module("../util/logger.js", () => ({
|
|
80
|
-
getLogger: (_name?: string) => makeFakeLoggerProxy(),
|
|
81
|
-
truncateForLog: (value: string) => value,
|
|
82
|
-
}));
|
|
83
|
-
|
|
84
|
-
// ── Permission checker — always allow so execution reaches the tool ──
|
|
85
|
-
mock.module("../permissions/checker.js", () => ({
|
|
86
|
-
classifyRisk: async () => ({ level: "low" }),
|
|
87
|
-
check: async () => ({ decision: "allow", reason: "allowed" }),
|
|
88
|
-
generateAllowlistOptions: () => [
|
|
89
|
-
{ label: "exact", description: "exact", pattern: "exact" },
|
|
90
|
-
],
|
|
91
|
-
generateScopeOptions: () => [{ label: "/tmp", scope: "/tmp" }],
|
|
92
|
-
}));
|
|
93
|
-
|
|
94
|
-
// ── Tool usage store stub ────────────────────────────────────────────
|
|
95
|
-
mock.module("../memory/tool-usage-store.js", () => ({
|
|
96
|
-
recordToolInvocation: () => {},
|
|
97
|
-
getRecentInvocations: () => [],
|
|
98
|
-
rotateToolInvocations: async () => 0,
|
|
99
|
-
}));
|
|
100
|
-
|
|
101
|
-
// ── Tool registry: return a stub tool whose execute records the call ─
|
|
102
|
-
let lastToolCall: { name: string; input: Record<string, unknown> } | undefined;
|
|
103
|
-
let fakeToolResult: ToolExecutionResult = {
|
|
104
|
-
content: "real tool output",
|
|
105
|
-
isError: false,
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
mock.module("../tools/registry.js", () => ({
|
|
109
|
-
getTool: (name: string) => {
|
|
110
|
-
if (name === "unknown_tool") return undefined;
|
|
111
|
-
return {
|
|
112
|
-
name,
|
|
113
|
-
description: "test tool",
|
|
114
|
-
category: "test",
|
|
115
|
-
defaultRiskLevel: "low",
|
|
116
|
-
input_schema: {},
|
|
117
|
-
execute: async (input: Record<string, unknown>) => {
|
|
118
|
-
lastToolCall = { name, input };
|
|
119
|
-
return fakeToolResult;
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
},
|
|
123
|
-
getAllTools: () => [],
|
|
124
|
-
}));
|
|
125
|
-
|
|
126
|
-
mock.module("../tools/shared/filesystem/path-policy.js", () => ({
|
|
127
|
-
sandboxPolicy: () => ({ ok: false }),
|
|
128
|
-
hostPolicy: () => ({ ok: false }),
|
|
129
|
-
}));
|
|
130
|
-
|
|
131
|
-
// ── Redaction + token manager so the executor's imports resolve ──────
|
|
132
|
-
mock.module("../security/redaction.js", () => ({
|
|
133
|
-
redactSensitiveFields: (input: Record<string, unknown>) => input,
|
|
134
|
-
}));
|
|
135
|
-
|
|
136
|
-
mock.module("../security/token-manager.js", () => ({
|
|
137
|
-
TokenExpiredError: class TokenExpiredError extends Error {},
|
|
138
|
-
}));
|
|
139
|
-
|
|
140
|
-
// ── Imports — after mock.module so the executor under test picks them up ──
|
|
141
|
-
import { PermissionPrompter } from "../permissions/prompter.js";
|
|
142
|
-
import { defaultToolExecutePlugin } from "../plugins/defaults/tool-execute/register.js";
|
|
143
|
-
import {
|
|
144
|
-
getMiddlewaresFor,
|
|
145
|
-
registerPlugin,
|
|
146
|
-
resetPluginRegistryForTests,
|
|
147
|
-
} from "../plugins/registry.js";
|
|
148
|
-
import type {
|
|
149
|
-
Middleware,
|
|
150
|
-
Plugin,
|
|
151
|
-
ToolExecuteArgs,
|
|
152
|
-
ToolExecuteResult,
|
|
153
|
-
} from "../plugins/types.js";
|
|
154
|
-
import { ToolExecutor } from "../tools/executor.js";
|
|
155
|
-
import type { ToolContext } from "../tools/types.js";
|
|
156
|
-
|
|
157
|
-
function makeContext(overrides?: Partial<ToolContext>): ToolContext {
|
|
158
|
-
return {
|
|
159
|
-
workingDir: "/tmp/project",
|
|
160
|
-
conversationId: "conversation-pipeline",
|
|
161
|
-
trustClass: "guardian",
|
|
162
|
-
...overrides,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function makePrompter(): PermissionPrompter {
|
|
167
|
-
return {
|
|
168
|
-
prompt: async () => ({ decision: "allow" as const }),
|
|
169
|
-
resolveConfirmation: () => {},
|
|
170
|
-
updateSender: () => {},
|
|
171
|
-
dispose: () => {},
|
|
172
|
-
} as unknown as PermissionPrompter;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
afterAll(() => {
|
|
176
|
-
mock.restore();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
beforeEach(() => {
|
|
180
|
-
resetPluginRegistryForTests();
|
|
181
|
-
lastToolCall = undefined;
|
|
182
|
-
fakeToolResult = { content: "real tool output", isError: false };
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe("ToolExecutor.execute → toolExecute pipeline", () => {
|
|
186
|
-
test("default pipeline (no plugins) runs the same execution path", async () => {
|
|
187
|
-
// With no plugins registered, the pipeline has an empty middleware
|
|
188
|
-
// array and the terminal (executeInternal) runs directly. The
|
|
189
|
-
// observable result must match the unwrapped behavior.
|
|
190
|
-
const executor = new ToolExecutor(makePrompter());
|
|
191
|
-
const result = await executor.execute(
|
|
192
|
-
"file_read",
|
|
193
|
-
{ path: "README.md" },
|
|
194
|
-
makeContext(),
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
expect(result.isError).toBe(false);
|
|
198
|
-
expect(result.content).toBe("real tool output");
|
|
199
|
-
expect(lastToolCall).toEqual({
|
|
200
|
-
name: "file_read",
|
|
201
|
-
input: { path: "README.md" },
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test("default tool-execute plugin: registering the passthrough preserves behavior", async () => {
|
|
206
|
-
// The default plugin is a passthrough whose middleware forwards to
|
|
207
|
-
// `next`. Registering it should not change observable behavior —
|
|
208
|
-
// the terminal still runs and returns the real tool result.
|
|
209
|
-
registerPlugin(defaultToolExecutePlugin);
|
|
210
|
-
|
|
211
|
-
// Sanity: the registry now reports exactly one middleware for the
|
|
212
|
-
// `toolExecute` slot, named `defaultToolExecute`.
|
|
213
|
-
const middlewares = getMiddlewaresFor("toolExecute");
|
|
214
|
-
expect(middlewares).toHaveLength(1);
|
|
215
|
-
expect(middlewares[0]?.name).toBe("defaultToolExecute");
|
|
216
|
-
|
|
217
|
-
const executor = new ToolExecutor(makePrompter());
|
|
218
|
-
const result = await executor.execute(
|
|
219
|
-
"file_read",
|
|
220
|
-
{ path: "README.md" },
|
|
221
|
-
makeContext(),
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
expect(result.isError).toBe(false);
|
|
225
|
-
expect(result.content).toBe("real tool output");
|
|
226
|
-
expect(lastToolCall).toEqual({
|
|
227
|
-
name: "file_read",
|
|
228
|
-
input: { path: "README.md" },
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test("spy middleware observes the full tool invocation (name, input, ctx)", async () => {
|
|
233
|
-
let observedArgs: ToolExecuteArgs | undefined;
|
|
234
|
-
let observedTurnCtx:
|
|
235
|
-
| { conversationId: string; requestId: string }
|
|
236
|
-
| undefined;
|
|
237
|
-
|
|
238
|
-
const spyMiddleware: Middleware<ToolExecuteArgs, ToolExecuteResult> =
|
|
239
|
-
async function spy(args, next, ctx) {
|
|
240
|
-
observedArgs = args;
|
|
241
|
-
observedTurnCtx = {
|
|
242
|
-
conversationId: ctx.conversationId,
|
|
243
|
-
requestId: ctx.requestId,
|
|
244
|
-
};
|
|
245
|
-
return next(args);
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const spyPlugin: Plugin = {
|
|
249
|
-
manifest: {
|
|
250
|
-
name: "spy-tool-execute",
|
|
251
|
-
version: "0.0.1",
|
|
252
|
-
},
|
|
253
|
-
middleware: { toolExecute: spyMiddleware },
|
|
254
|
-
};
|
|
255
|
-
registerPlugin(spyPlugin);
|
|
256
|
-
|
|
257
|
-
const executor = new ToolExecutor(makePrompter());
|
|
258
|
-
const ctx = makeContext({
|
|
259
|
-
conversationId: "conv-spy",
|
|
260
|
-
requestId: "req-spy",
|
|
261
|
-
});
|
|
262
|
-
const result = await executor.execute(
|
|
263
|
-
"bash",
|
|
264
|
-
{ command: "echo hi", timeout_seconds: 10 },
|
|
265
|
-
ctx,
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
// Spy observed the full args
|
|
269
|
-
expect(observedArgs).toBeDefined();
|
|
270
|
-
expect(observedArgs!.name).toBe("bash");
|
|
271
|
-
expect(observedArgs!.input).toEqual({
|
|
272
|
-
command: "echo hi",
|
|
273
|
-
timeout_seconds: 10,
|
|
274
|
-
});
|
|
275
|
-
expect(observedArgs!.context).toBe(ctx);
|
|
276
|
-
|
|
277
|
-
// Spy observed the synthesized TurnContext carrying conversation +
|
|
278
|
-
// request IDs from the ToolContext.
|
|
279
|
-
expect(observedTurnCtx).toEqual({
|
|
280
|
-
conversationId: "conv-spy",
|
|
281
|
-
requestId: "req-spy",
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// Terminal still ran — result reflects the real tool output.
|
|
285
|
-
expect(result.isError).toBe(false);
|
|
286
|
-
expect(result.content).toBe("real tool output");
|
|
287
|
-
expect(lastToolCall).toEqual({
|
|
288
|
-
name: "bash",
|
|
289
|
-
input: { command: "echo hi", timeout_seconds: 10 },
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
test("short-circuit middleware intercepts and supplies a custom result", async () => {
|
|
294
|
-
const syntheticResult: ToolExecuteResult = {
|
|
295
|
-
content: "synthesized by middleware",
|
|
296
|
-
isError: false,
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
const shortCircuit: Middleware<ToolExecuteArgs, ToolExecuteResult> =
|
|
300
|
-
async function shortCircuitMw(_args, _next) {
|
|
301
|
-
// Intentionally omit `next` — the terminal (real tool execution)
|
|
302
|
-
// must not run.
|
|
303
|
-
return syntheticResult;
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const interceptPlugin: Plugin = {
|
|
307
|
-
manifest: {
|
|
308
|
-
name: "short-circuit-tool-execute",
|
|
309
|
-
version: "0.0.1",
|
|
310
|
-
},
|
|
311
|
-
middleware: { toolExecute: shortCircuit },
|
|
312
|
-
};
|
|
313
|
-
registerPlugin(interceptPlugin);
|
|
314
|
-
|
|
315
|
-
const executor = new ToolExecutor(makePrompter());
|
|
316
|
-
const result = await executor.execute(
|
|
317
|
-
"file_write",
|
|
318
|
-
{ path: "dangerous.txt", content: "should not run" },
|
|
319
|
-
makeContext(),
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
expect(result).toEqual(syntheticResult);
|
|
323
|
-
// The real tool execute must NOT have been called.
|
|
324
|
-
expect(lastToolCall).toBeUndefined();
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
test("slow middleware does not trip a pipeline-level timeout", async () => {
|
|
328
|
-
// Regression: the pipeline must NOT arm a timer — `executeWithTimeout`
|
|
329
|
-
// inside `executeInternal` is the sole enforcer of the per-tool budget
|
|
330
|
-
// and only wraps the actual tool call. Upstream phases (permission
|
|
331
|
-
// checks, approval waits, middleware) must not race the tool budget,
|
|
332
|
-
// because that would break the `execute()` never-throws contract when
|
|
333
|
-
// a slow phase (e.g. a human clicking "allow") exceeds the budget.
|
|
334
|
-
const slow: Middleware<ToolExecuteArgs, ToolExecuteResult> =
|
|
335
|
-
async function slowMw(args, next) {
|
|
336
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
337
|
-
return next(args);
|
|
338
|
-
};
|
|
339
|
-
registerPlugin({
|
|
340
|
-
manifest: {
|
|
341
|
-
name: "slow-tool-execute",
|
|
342
|
-
version: "0.0.1",
|
|
343
|
-
},
|
|
344
|
-
middleware: { toolExecute: slow },
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
const prev = mockConfig.timeouts.toolExecutionTimeoutSec;
|
|
348
|
-
mockConfig.timeouts.toolExecutionTimeoutSec = 0.01;
|
|
349
|
-
try {
|
|
350
|
-
const executor = new ToolExecutor(makePrompter());
|
|
351
|
-
const result = await executor.execute(
|
|
352
|
-
"file_read",
|
|
353
|
-
{ path: "README.md" },
|
|
354
|
-
makeContext(),
|
|
355
|
-
);
|
|
356
|
-
// Middleware phase (50ms) exceeds the per-tool budget (10ms), but
|
|
357
|
-
// that budget is only enforced inside `executeWithTimeout` around
|
|
358
|
-
// the tool invocation itself. The terminal runs and succeeds.
|
|
359
|
-
expect(result.isError).toBe(false);
|
|
360
|
-
expect(result.content).toBe("real tool output");
|
|
361
|
-
} finally {
|
|
362
|
-
mockConfig.timeouts.toolExecutionTimeoutSec = prev;
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
test("multiple middlewares compose in registration order (outer-first)", async () => {
|
|
367
|
-
const trace: string[] = [];
|
|
368
|
-
|
|
369
|
-
const outer: Middleware<ToolExecuteArgs, ToolExecuteResult> =
|
|
370
|
-
async function outerMw(args, next) {
|
|
371
|
-
trace.push("outer:before");
|
|
372
|
-
const result = await next(args);
|
|
373
|
-
trace.push("outer:after");
|
|
374
|
-
return result;
|
|
375
|
-
};
|
|
376
|
-
const inner: Middleware<ToolExecuteArgs, ToolExecuteResult> =
|
|
377
|
-
async function innerMw(args, next) {
|
|
378
|
-
trace.push("inner:before");
|
|
379
|
-
const result = await next(args);
|
|
380
|
-
trace.push("inner:after");
|
|
381
|
-
return result;
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
registerPlugin({
|
|
385
|
-
manifest: {
|
|
386
|
-
name: "outer-tool-execute",
|
|
387
|
-
version: "0.0.1",
|
|
388
|
-
},
|
|
389
|
-
middleware: { toolExecute: outer },
|
|
390
|
-
});
|
|
391
|
-
registerPlugin({
|
|
392
|
-
manifest: {
|
|
393
|
-
name: "inner-tool-execute",
|
|
394
|
-
version: "0.0.1",
|
|
395
|
-
},
|
|
396
|
-
middleware: { toolExecute: inner },
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
const executor = new ToolExecutor(makePrompter());
|
|
400
|
-
const result = await executor.execute(
|
|
401
|
-
"file_read",
|
|
402
|
-
{ path: "README.md" },
|
|
403
|
-
makeContext(),
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
expect(result.isError).toBe(false);
|
|
407
|
-
// Outer middleware wraps inner (registration order = onion order),
|
|
408
|
-
// so the trace is outer:before → inner:before → terminal →
|
|
409
|
-
// inner:after → outer:after.
|
|
410
|
-
expect(trace).toEqual([
|
|
411
|
-
"outer:before",
|
|
412
|
-
"inner:before",
|
|
413
|
-
"inner:after",
|
|
414
|
-
"outer:after",
|
|
415
|
-
]);
|
|
416
|
-
});
|
|
417
|
-
});
|