@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,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Echo plugin — observes every assistant pipeline and logs one structured
|
|
3
|
-
* line per invocation to stderr.
|
|
4
|
-
*
|
|
5
|
-
* Bundled in the repository as an authoring reference. To try it locally,
|
|
6
|
-
* symlink (or copy) this directory into `<workspaceDir>/plugins/echo/` and
|
|
7
|
-
* restart the assistant. See `README.md` in this directory for the install
|
|
8
|
-
* recipe and `assistant/docs/plugins.md` for general plugin authoring docs.
|
|
9
|
-
*
|
|
10
|
-
* ## Runtime bridge
|
|
11
|
-
*
|
|
12
|
-
* The plugin reads `registerPlugin` from `globalThis.__vellumPluginRuntime`,
|
|
13
|
-
* a stable handle the daemon attaches at startup. This lets the same plugin
|
|
14
|
-
* file work whether the daemon is running from source (relative or absolute
|
|
15
|
-
* imports would resolve to the daemon's modules) or as a `bun --compile`
|
|
16
|
-
* binary (where absolute imports would load a disjoint disk copy with a
|
|
17
|
-
* separate registry instance). The bridge is documented in
|
|
18
|
-
* `assistant/src/plugins/external-api.ts`.
|
|
19
|
-
*
|
|
20
|
-
* Type imports below still come from the in-repo source tree. Types are
|
|
21
|
-
* erased at runtime, so they don't affect module identity — but they only
|
|
22
|
-
* resolve while this file lives inside the vellum-assistant checkout. For a
|
|
23
|
-
* standalone-copy install, rewrite the `import type` paths to absolute paths
|
|
24
|
-
* inside a checkout (or vendor only the types you need).
|
|
25
|
-
*
|
|
26
|
-
* ## Design
|
|
27
|
-
*
|
|
28
|
-
* - Registers an observer middleware on every slot of `PipelineMiddlewareMap`.
|
|
29
|
-
* - Each middleware records a start timestamp, calls `next(args)`, and on
|
|
30
|
-
* return — whether successful or not — emits one JSON line on `stderr` with
|
|
31
|
-
* `{ plugin, pipeline, durationMs, outcome }`. A `try { return await next(); }
|
|
32
|
-
* catch { outcome = "error"; rethrow; } finally { log(); }` pattern keeps the
|
|
33
|
-
* observation strictly non-interfering: the plugin never swallows errors
|
|
34
|
-
* and never rewrites arguments or results.
|
|
35
|
-
* - Middleware is declared as async functions with stable names so the
|
|
36
|
-
* pipeline runner's `chain` log field attributes them correctly.
|
|
37
|
-
*
|
|
38
|
-
* The file exports no named symbols at module level — it only runs
|
|
39
|
-
* `registerPlugin(echoPlugin)` as an import-time side effect, matching the
|
|
40
|
-
* user-plugin-loader contract (see `assistant/src/plugins/user-loader.ts`).
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
import type {
|
|
44
|
-
ToolResultTruncateArgs,
|
|
45
|
-
ToolResultTruncateResult,
|
|
46
|
-
} from "../../../src/plugins/defaults/tool-result-truncate/types.js";
|
|
47
|
-
import type { VellumPluginRuntime } from "../../../src/plugins/external-api.js";
|
|
48
|
-
import type {
|
|
49
|
-
CircuitBreakerArgs,
|
|
50
|
-
CircuitBreakerResult,
|
|
51
|
-
CompactionArgs,
|
|
52
|
-
CompactionResult,
|
|
53
|
-
EmptyResponseArgs,
|
|
54
|
-
EmptyResponseResult,
|
|
55
|
-
LLMCallArgs,
|
|
56
|
-
LLMCallResult,
|
|
57
|
-
MemoryArgs,
|
|
58
|
-
MemoryResult,
|
|
59
|
-
OverflowReduceArgs,
|
|
60
|
-
OverflowReduceResult,
|
|
61
|
-
PersistArgs,
|
|
62
|
-
PersistResult,
|
|
63
|
-
Plugin,
|
|
64
|
-
TitleArgs,
|
|
65
|
-
TitleResult,
|
|
66
|
-
TokenEstimateArgs,
|
|
67
|
-
TokenEstimateResult,
|
|
68
|
-
ToolErrorArgs,
|
|
69
|
-
ToolErrorResult,
|
|
70
|
-
ToolExecuteArgs,
|
|
71
|
-
ToolExecuteResult,
|
|
72
|
-
TurnArgs,
|
|
73
|
-
TurnResult,
|
|
74
|
-
} from "../../../src/plugins/types.js";
|
|
75
|
-
|
|
76
|
-
const runtime = (globalThis as { __vellumPluginRuntime?: VellumPluginRuntime })
|
|
77
|
-
.__vellumPluginRuntime;
|
|
78
|
-
if (!runtime || runtime.version !== 1) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
"echo plugin: globalThis.__vellumPluginRuntime is missing or has an unexpected version — install a recent assistant build",
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
const { registerPlugin } = runtime;
|
|
84
|
-
|
|
85
|
-
const PLUGIN_NAME = "echo";
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* One line written to stderr per pipeline invocation. Kept intentionally
|
|
89
|
-
* compact — pino-style JSON so operators can pipe the assistant's stderr
|
|
90
|
-
* through `jq` without reshaping.
|
|
91
|
-
*/
|
|
92
|
-
function emit(
|
|
93
|
-
pipelineName: string,
|
|
94
|
-
startMs: number,
|
|
95
|
-
outcome: "success" | "error",
|
|
96
|
-
): void {
|
|
97
|
-
const durationMs = Math.round(performance.now() - startMs);
|
|
98
|
-
const record = {
|
|
99
|
-
plugin: PLUGIN_NAME,
|
|
100
|
-
pipeline: pipelineName,
|
|
101
|
-
durationMs,
|
|
102
|
-
outcome,
|
|
103
|
-
};
|
|
104
|
-
process.stderr.write(`${JSON.stringify(record)}\n`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Factory for a pipeline-agnostic observer middleware. The returned function
|
|
109
|
-
* carries a `name` so `runPipeline`'s `chain` log field attributes this
|
|
110
|
-
* plugin's frame correctly. Error paths rethrow — the plugin is purely
|
|
111
|
-
* observational and must never change the turn's outcome.
|
|
112
|
-
*/
|
|
113
|
-
function makeObserver<A, R>(
|
|
114
|
-
pipelineName: string,
|
|
115
|
-
): (args: A, next: (args: A) => Promise<R>, _ctx: unknown) => Promise<R> {
|
|
116
|
-
const fn = async function echoObserver(
|
|
117
|
-
args: A,
|
|
118
|
-
next: (args: A) => Promise<R>,
|
|
119
|
-
_ctx: unknown,
|
|
120
|
-
): Promise<R> {
|
|
121
|
-
const start = performance.now();
|
|
122
|
-
let outcome: "success" | "error" = "success";
|
|
123
|
-
try {
|
|
124
|
-
return await next(args);
|
|
125
|
-
} catch (err) {
|
|
126
|
-
outcome = "error";
|
|
127
|
-
throw err;
|
|
128
|
-
} finally {
|
|
129
|
-
emit(pipelineName, start, outcome);
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
return fn;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* The echo plugin. Declares one middleware per slot in
|
|
137
|
-
* `PipelineMiddlewareMap` — all thin observers produced by `makeObserver`.
|
|
138
|
-
*
|
|
139
|
-
* Manifest:
|
|
140
|
-
* - Host-compat range lives in `package.json` under
|
|
141
|
-
* `peerDependencies["@vellumai/plugin-api"]`. The external-plugin loader
|
|
142
|
-
* validates it against the running assistant version via
|
|
143
|
-
* `semver.satisfies()` before this file is even imported.
|
|
144
|
-
* - No `requiresCredential` or `requiresFlag` — the plugin needs no external
|
|
145
|
-
* state and runs unconditionally.
|
|
146
|
-
*/
|
|
147
|
-
const echoPlugin: Plugin = {
|
|
148
|
-
manifest: {
|
|
149
|
-
name: PLUGIN_NAME,
|
|
150
|
-
version: "0.1.0",
|
|
151
|
-
},
|
|
152
|
-
middleware: {
|
|
153
|
-
turn: makeObserver<TurnArgs, TurnResult>("turn"),
|
|
154
|
-
llmCall: makeObserver<LLMCallArgs, LLMCallResult>("llmCall"),
|
|
155
|
-
toolExecute: makeObserver<ToolExecuteArgs, ToolExecuteResult>(
|
|
156
|
-
"toolExecute",
|
|
157
|
-
),
|
|
158
|
-
memoryRetrieval: makeObserver<MemoryArgs, MemoryResult>("memoryRetrieval"),
|
|
159
|
-
tokenEstimate: makeObserver<TokenEstimateArgs, TokenEstimateResult>(
|
|
160
|
-
"tokenEstimate",
|
|
161
|
-
),
|
|
162
|
-
compaction: makeObserver<CompactionArgs, CompactionResult>("compaction"),
|
|
163
|
-
overflowReduce: makeObserver<OverflowReduceArgs, OverflowReduceResult>(
|
|
164
|
-
"overflowReduce",
|
|
165
|
-
),
|
|
166
|
-
persistence: makeObserver<PersistArgs, PersistResult>("persistence"),
|
|
167
|
-
titleGenerate: makeObserver<TitleArgs, TitleResult>("titleGenerate"),
|
|
168
|
-
toolResultTruncate: makeObserver<
|
|
169
|
-
ToolResultTruncateArgs,
|
|
170
|
-
ToolResultTruncateResult
|
|
171
|
-
>("toolResultTruncate"),
|
|
172
|
-
emptyResponse: makeObserver<EmptyResponseArgs, EmptyResponseResult>(
|
|
173
|
-
"emptyResponse",
|
|
174
|
-
),
|
|
175
|
-
toolError: makeObserver<ToolErrorArgs, ToolErrorResult>("toolError"),
|
|
176
|
-
circuitBreaker: makeObserver<CircuitBreakerArgs, CircuitBreakerResult>(
|
|
177
|
-
"circuitBreaker",
|
|
178
|
-
),
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
// Side-effect registration — the user-plugin loader dynamic-imports this
|
|
183
|
-
// file and expects the registry to pick up the plugin during that import.
|
|
184
|
-
registerPlugin(echoPlugin);
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
BOOTSTRAP_CLEANUP_USER_TURN_THRESHOLD,
|
|
5
|
-
countBootstrapUserTurns,
|
|
6
|
-
shouldCleanupBootstrapAfterTurn,
|
|
7
|
-
} from "../daemon/bootstrap-turn-cleanup.js";
|
|
8
|
-
|
|
9
|
-
function message(role: string, content: string) {
|
|
10
|
-
return { role, content };
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
describe("bootstrap turn cleanup", () => {
|
|
14
|
-
test("does not count the hidden wake-up greeting as a user turn", () => {
|
|
15
|
-
const messages = [
|
|
16
|
-
message(
|
|
17
|
-
"user",
|
|
18
|
-
JSON.stringify([{ type: "text", text: "Wake up, my friend." }]),
|
|
19
|
-
),
|
|
20
|
-
message("assistant", "hello"),
|
|
21
|
-
message("user", "real request"),
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
expect(countBootstrapUserTurns(messages)).toBe(1);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("cleans up after the configured user-turn threshold", () => {
|
|
28
|
-
const messages = Array.from(
|
|
29
|
-
{ length: BOOTSTRAP_CLEANUP_USER_TURN_THRESHOLD },
|
|
30
|
-
(_value, index) => message("user", `request ${index + 1}`),
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
expect(shouldCleanupBootstrapAfterTurn(messages)).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("keeps bootstrap before the configured user-turn threshold", () => {
|
|
37
|
-
const messages = Array.from(
|
|
38
|
-
{ length: BOOTSTRAP_CLEANUP_USER_TURN_THRESHOLD - 1 },
|
|
39
|
-
(_value, index) => message("user", `request ${index + 1}`),
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
expect(shouldCleanupBootstrapAfterTurn(messages)).toBe(false);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1,405 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the `circuitBreaker` plugin pipeline.
|
|
3
|
-
*
|
|
4
|
-
* The default plugin (`plugins/defaults/circuit-breaker/register.ts`) replaces the
|
|
5
|
-
* inline compaction circuit-breaker logic that previously lived in
|
|
6
|
-
* `daemon/conversation-agent-loop.ts`. These tests exercise the default
|
|
7
|
-
* plugin through the pipeline runner and assert the threshold (3 consecutive
|
|
8
|
-
* failures) and cooldown (1 hour) exactly match the legacy behavior.
|
|
9
|
-
*
|
|
10
|
-
* Coverage mirrors the eight scenarios the deleted
|
|
11
|
-
* `compaction-circuit-breaker.test.ts` exercised before the wrap:
|
|
12
|
-
* (a) counter increments on each failure outcome
|
|
13
|
-
* (b) circuit opens after exactly 3 consecutive failures
|
|
14
|
-
* (c) successful compaction resets counter and clears the circuit
|
|
15
|
-
* (d) decision.open reflects state and cooldown expiry
|
|
16
|
-
* (d) open circuit admits force:true (exercised at the call site; this
|
|
17
|
-
* file asserts decision.open is true while the breaker is tripped)
|
|
18
|
-
* (e) circuit re-opens after cooldown expiry when 3 more failures
|
|
19
|
-
* accumulate (guards the stale-timestamp regression)
|
|
20
|
-
* (f) callers skip tracking on undefined summaryFailed so early returns
|
|
21
|
-
* don't reset the counter (documented from the caller's perspective)
|
|
22
|
-
* (g) open→closed transition emits `compaction_circuit_closed` exactly once
|
|
23
|
-
* (h) closed→closed transition emits nothing
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
27
|
-
|
|
28
|
-
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
29
|
-
import type { TrustContext } from "../daemon/trust-context.js";
|
|
30
|
-
import {
|
|
31
|
-
COMPACTION_CIRCUIT_COOLDOWN_MS,
|
|
32
|
-
COMPACTION_CIRCUIT_FAILURE_THRESHOLD,
|
|
33
|
-
} from "../plugins/defaults/circuit-breaker/middlewares/circuitBreaker.js";
|
|
34
|
-
import { defaultCircuitBreakerPlugin } from "../plugins/defaults/circuit-breaker/register.js";
|
|
35
|
-
import { runPipeline } from "../plugins/pipeline.js";
|
|
36
|
-
import {
|
|
37
|
-
getMiddlewaresFor,
|
|
38
|
-
registerPlugin,
|
|
39
|
-
resetPluginRegistryForTests,
|
|
40
|
-
} from "../plugins/registry.js";
|
|
41
|
-
import type {
|
|
42
|
-
CircuitBreakerArgs,
|
|
43
|
-
CircuitBreakerResult,
|
|
44
|
-
TurnContext,
|
|
45
|
-
} from "../plugins/types.js";
|
|
46
|
-
|
|
47
|
-
// ─── Fixtures ───────────────────────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
interface BreakerState {
|
|
50
|
-
readonly conversationId: string;
|
|
51
|
-
consecutiveCompactionFailures: number;
|
|
52
|
-
compactionCircuitOpenUntil: number | null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function makeState(conversationId = "conv-breaker-test"): BreakerState {
|
|
56
|
-
return {
|
|
57
|
-
conversationId,
|
|
58
|
-
consecutiveCompactionFailures: 0,
|
|
59
|
-
compactionCircuitOpenUntil: null,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function collectEvents(): {
|
|
64
|
-
events: ServerMessage[];
|
|
65
|
-
onEvent: (msg: ServerMessage) => void;
|
|
66
|
-
} {
|
|
67
|
-
const events: ServerMessage[] = [];
|
|
68
|
-
return { events, onEvent: (msg) => events.push(msg) };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const trust: TrustContext = {
|
|
72
|
-
sourceChannel: "vellum",
|
|
73
|
-
trustClass: "guardian",
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
function makeTurnCtx(conversationId = "conv-breaker-test"): TurnContext {
|
|
77
|
-
return {
|
|
78
|
-
requestId: "req-test",
|
|
79
|
-
conversationId,
|
|
80
|
-
turnIndex: 0,
|
|
81
|
-
trust,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Run the `circuitBreaker` pipeline through the registered plugin chain.
|
|
87
|
-
* Mirrors how `conversation-agent-loop.ts` invokes it, with the same
|
|
88
|
-
* terminal fallback used in production.
|
|
89
|
-
*/
|
|
90
|
-
async function runCircuit(
|
|
91
|
-
args: CircuitBreakerArgs,
|
|
92
|
-
ctx: TurnContext = makeTurnCtx(args.state.conversationId),
|
|
93
|
-
): Promise<CircuitBreakerResult> {
|
|
94
|
-
return runPipeline<CircuitBreakerArgs, CircuitBreakerResult>(
|
|
95
|
-
"circuitBreaker",
|
|
96
|
-
getMiddlewaresFor("circuitBreaker"),
|
|
97
|
-
async (terminalArgs) => {
|
|
98
|
-
const openUntil = terminalArgs.state.compactionCircuitOpenUntil;
|
|
99
|
-
const now = Date.now();
|
|
100
|
-
if (openUntil !== null && now < openUntil) {
|
|
101
|
-
return { open: true, cooldownRemainingMs: openUntil - now };
|
|
102
|
-
}
|
|
103
|
-
return { open: false };
|
|
104
|
-
},
|
|
105
|
-
args,
|
|
106
|
-
ctx,
|
|
107
|
-
500,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
describe("circuit-breaker pipeline", () => {
|
|
112
|
-
let originalDateNow: () => number;
|
|
113
|
-
|
|
114
|
-
beforeEach(() => {
|
|
115
|
-
resetPluginRegistryForTests();
|
|
116
|
-
registerPlugin(defaultCircuitBreakerPlugin);
|
|
117
|
-
originalDateNow = Date.now;
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
afterEach(() => {
|
|
121
|
-
Date.now = originalDateNow;
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("threshold and cooldown match legacy constants exactly", () => {
|
|
125
|
-
// Sanity — the plugin must expose the same constants the legacy inline
|
|
126
|
-
// helpers used. Any drift would silently change user-visible behavior.
|
|
127
|
-
expect(COMPACTION_CIRCUIT_FAILURE_THRESHOLD).toBe(3);
|
|
128
|
-
expect(COMPACTION_CIRCUIT_COOLDOWN_MS).toBe(60 * 60 * 1000);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("(a) counter increments on each failure outcome", async () => {
|
|
132
|
-
const state = makeState();
|
|
133
|
-
const { onEvent, events } = collectEvents();
|
|
134
|
-
|
|
135
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
136
|
-
expect(state.consecutiveCompactionFailures).toBe(1);
|
|
137
|
-
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
138
|
-
expect(events).toHaveLength(0);
|
|
139
|
-
|
|
140
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
141
|
-
expect(state.consecutiveCompactionFailures).toBe(2);
|
|
142
|
-
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
143
|
-
expect(events).toHaveLength(0);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test("(b) circuit opens after exactly 3 consecutive failures", async () => {
|
|
147
|
-
const fixedNow = 1_700_000_000_000;
|
|
148
|
-
Date.now = () => fixedNow;
|
|
149
|
-
|
|
150
|
-
const state = makeState();
|
|
151
|
-
const { onEvent, events } = collectEvents();
|
|
152
|
-
|
|
153
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
154
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
155
|
-
// Two failures — circuit still closed.
|
|
156
|
-
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
157
|
-
expect(events).toHaveLength(0);
|
|
158
|
-
|
|
159
|
-
const third = await runCircuit({
|
|
160
|
-
key: "k",
|
|
161
|
-
outcome: "failure",
|
|
162
|
-
state,
|
|
163
|
-
onEvent,
|
|
164
|
-
});
|
|
165
|
-
// Third failure — circuit trips and fires the event exactly once.
|
|
166
|
-
expect(state.consecutiveCompactionFailures).toBe(3);
|
|
167
|
-
expect(state.compactionCircuitOpenUntil).toBe(fixedNow + 60 * 60 * 1000);
|
|
168
|
-
expect(third.open).toBe(true);
|
|
169
|
-
expect(third.cooldownRemainingMs).toBe(60 * 60 * 1000);
|
|
170
|
-
expect(events).toHaveLength(1);
|
|
171
|
-
expect(events[0]).toEqual({
|
|
172
|
-
type: "compaction_circuit_open",
|
|
173
|
-
conversationId: state.conversationId,
|
|
174
|
-
reason: "3_consecutive_failures",
|
|
175
|
-
openUntil: fixedNow + 60 * 60 * 1000,
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// Further failures do not re-fire the event while the circuit is open.
|
|
179
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
180
|
-
expect(state.consecutiveCompactionFailures).toBe(4);
|
|
181
|
-
expect(events).toHaveLength(1);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test("(c) successful outcome resets counter and clears circuit", async () => {
|
|
185
|
-
const fixedNow = 1_700_000_000_000;
|
|
186
|
-
Date.now = () => fixedNow;
|
|
187
|
-
|
|
188
|
-
const state = makeState();
|
|
189
|
-
const { onEvent } = collectEvents();
|
|
190
|
-
|
|
191
|
-
// Trip the breaker.
|
|
192
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
193
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
194
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
195
|
-
expect(state.compactionCircuitOpenUntil).not.toBeNull();
|
|
196
|
-
|
|
197
|
-
// Success resets state.
|
|
198
|
-
await runCircuit({ key: "k", outcome: "success", state, onEvent });
|
|
199
|
-
expect(state.consecutiveCompactionFailures).toBe(0);
|
|
200
|
-
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test("(d) decision.open reflects state and expiry", async () => {
|
|
204
|
-
const fixedNow = 1_700_000_000_000;
|
|
205
|
-
Date.now = () => fixedNow;
|
|
206
|
-
|
|
207
|
-
const state = makeState();
|
|
208
|
-
const { onEvent } = collectEvents();
|
|
209
|
-
|
|
210
|
-
// Query-only on a fresh state: closed, no cooldown.
|
|
211
|
-
const preQuery = await runCircuit({ key: "k", state, onEvent });
|
|
212
|
-
expect(preQuery.open).toBe(false);
|
|
213
|
-
expect(preQuery.cooldownRemainingMs).toBeUndefined();
|
|
214
|
-
|
|
215
|
-
// Trip the breaker.
|
|
216
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
217
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
218
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
219
|
-
|
|
220
|
-
// Query-only while open: open + non-zero cooldown.
|
|
221
|
-
const openQuery = await runCircuit({ key: "k", state, onEvent });
|
|
222
|
-
expect(openQuery.open).toBe(true);
|
|
223
|
-
expect(openQuery.cooldownRemainingMs).toBe(60 * 60 * 1000);
|
|
224
|
-
|
|
225
|
-
// After cooldown expires the breaker reports closed again, even without
|
|
226
|
-
// an explicit reset — the open-until timestamp is the only source of
|
|
227
|
-
// truth for the gate.
|
|
228
|
-
Date.now = () => fixedNow + 60 * 60 * 1000 + 1;
|
|
229
|
-
const postCooldown = await runCircuit({ key: "k", state, onEvent });
|
|
230
|
-
expect(postCooldown.open).toBe(false);
|
|
231
|
-
expect(postCooldown.cooldownRemainingMs).toBeUndefined();
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test("(e) circuit re-opens after cooldown expiry when 3 more failures accumulate", async () => {
|
|
235
|
-
// Regression: before the fix in the legacy helper, opening the breaker a
|
|
236
|
-
// second time required `compactionCircuitOpenUntil === null`. Once a
|
|
237
|
-
// cooldown expired, the decision correctly reported "closed" but the
|
|
238
|
-
// stale past-timestamp stayed on the state, so the next 3-strike window
|
|
239
|
-
// couldn't trip a new cooldown. The default plugin must treat any
|
|
240
|
-
// expired timestamp the same as null.
|
|
241
|
-
const t0 = 1_700_000_000_000;
|
|
242
|
-
Date.now = () => t0;
|
|
243
|
-
|
|
244
|
-
const state = makeState();
|
|
245
|
-
const { onEvent, events } = collectEvents();
|
|
246
|
-
|
|
247
|
-
// Trip the breaker the first time.
|
|
248
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
249
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
250
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
251
|
-
expect(state.compactionCircuitOpenUntil).toBe(t0 + 60 * 60 * 1000);
|
|
252
|
-
expect(events).toHaveLength(1);
|
|
253
|
-
|
|
254
|
-
// Advance past the cooldown window. Manually reset the counter — in
|
|
255
|
-
// production this happens when a subsequent `maybeCompact` call succeeds
|
|
256
|
-
// (outcome: "success") after the cooldown elapses, but the bug
|
|
257
|
-
// manifests even when the counter is reset: the stale
|
|
258
|
-
// `compactionCircuitOpenUntil` is what breaks re-opening.
|
|
259
|
-
const t1 = t0 + 60 * 60 * 1000 + 1;
|
|
260
|
-
Date.now = () => t1;
|
|
261
|
-
const postCooldown = await runCircuit({ key: "k", state, onEvent });
|
|
262
|
-
expect(postCooldown.open).toBe(false);
|
|
263
|
-
state.consecutiveCompactionFailures = 0;
|
|
264
|
-
// `compactionCircuitOpenUntil` is deliberately left as the old
|
|
265
|
-
// timestamp to reproduce the bug condition.
|
|
266
|
-
expect(state.compactionCircuitOpenUntil).toBe(t0 + 60 * 60 * 1000);
|
|
267
|
-
|
|
268
|
-
// Three more failures must trip a fresh cooldown even though the old
|
|
269
|
-
// timestamp is still set.
|
|
270
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
271
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
272
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
273
|
-
expect(state.consecutiveCompactionFailures).toBe(3);
|
|
274
|
-
expect(state.compactionCircuitOpenUntil).toBe(t1 + 60 * 60 * 1000);
|
|
275
|
-
expect(events).toHaveLength(2);
|
|
276
|
-
expect(events[1]).toEqual({
|
|
277
|
-
type: "compaction_circuit_open",
|
|
278
|
-
conversationId: state.conversationId,
|
|
279
|
-
reason: "3_consecutive_failures",
|
|
280
|
-
openUntil: t1 + 60 * 60 * 1000,
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
test("(f) callers must skip tracking on undefined summaryFailed so early returns don't reset the counter", async () => {
|
|
285
|
-
// Regression: `maybeCompact()` returns `summaryFailed: undefined` on
|
|
286
|
-
// early-return paths (no eligible messages, below threshold, cooldown
|
|
287
|
-
// active, truncation-only). Callers guard with `summaryFailed !==
|
|
288
|
-
// undefined` at every call site — this test asserts that a query-only
|
|
289
|
-
// pipeline invocation (no `outcome`) preserves the counter.
|
|
290
|
-
const state = makeState();
|
|
291
|
-
const { onEvent } = collectEvents();
|
|
292
|
-
|
|
293
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
294
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
295
|
-
expect(state.consecutiveCompactionFailures).toBe(2);
|
|
296
|
-
|
|
297
|
-
// Query-only — should NOT touch the counter.
|
|
298
|
-
await runCircuit({ key: "k", state, onEvent });
|
|
299
|
-
expect(state.consecutiveCompactionFailures).toBe(2);
|
|
300
|
-
|
|
301
|
-
// A third real failure then trips the breaker as expected.
|
|
302
|
-
await runCircuit({ key: "k", outcome: "failure", state, onEvent });
|
|
303
|
-
expect(state.consecutiveCompactionFailures).toBe(3);
|
|
304
|
-
expect(state.compactionCircuitOpenUntil).not.toBeNull();
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
test("(g) open→closed transition emits compaction_circuit_closed exactly once", async () => {
|
|
308
|
-
// Regression: before this fix in the legacy helper, the reset branch
|
|
309
|
-
// silently cleared `compactionCircuitOpenUntil` without notifying the
|
|
310
|
-
// client. The Swift banner set from `compaction_circuit_open` would
|
|
311
|
-
// stay visible until the original `openUntil` deadline (up to 1h),
|
|
312
|
-
// misrepresenting the live state. The default plugin emits
|
|
313
|
-
// `compaction_circuit_closed` on the open→closed transition so the
|
|
314
|
-
// banner dismisses immediately.
|
|
315
|
-
const fixedNow = 1_700_000_000_000;
|
|
316
|
-
Date.now = () => fixedNow;
|
|
317
|
-
|
|
318
|
-
const state = makeState();
|
|
319
|
-
const { onEvent, events } = collectEvents();
|
|
320
|
-
|
|
321
|
-
// Force the circuit into the open state directly — the emitted-event
|
|
322
|
-
// transition logic is what we're testing, not the tripping path.
|
|
323
|
-
state.compactionCircuitOpenUntil = fixedNow + 60 * 60 * 1000;
|
|
324
|
-
state.consecutiveCompactionFailures = 3;
|
|
325
|
-
|
|
326
|
-
await runCircuit({ key: "k", outcome: "success", state, onEvent });
|
|
327
|
-
|
|
328
|
-
expect(state.consecutiveCompactionFailures).toBe(0);
|
|
329
|
-
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
330
|
-
expect(events).toHaveLength(1);
|
|
331
|
-
expect(events[0]).toEqual({
|
|
332
|
-
type: "compaction_circuit_closed",
|
|
333
|
-
conversationId: state.conversationId,
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
test("(h) successful outcome against an already-closed circuit emits no event", async () => {
|
|
338
|
-
// Emitting `compaction_circuit_closed` on every successful compaction
|
|
339
|
-
// would spam the client (the breaker is closed in the common case).
|
|
340
|
-
// Only the open→closed transition is meaningful.
|
|
341
|
-
const state = makeState();
|
|
342
|
-
const { onEvent, events } = collectEvents();
|
|
343
|
-
|
|
344
|
-
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
345
|
-
await runCircuit({ key: "k", outcome: "success", state, onEvent });
|
|
346
|
-
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
347
|
-
expect(events).toHaveLength(0);
|
|
348
|
-
|
|
349
|
-
// A second successful outcome while still closed — still no event.
|
|
350
|
-
await runCircuit({ key: "k", outcome: "success", state, onEvent });
|
|
351
|
-
expect(events).toHaveLength(0);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
test("omitting onEvent still updates state without emitting", async () => {
|
|
355
|
-
// `onEvent` is optional in `CircuitBreakerArgs`. When omitted the plugin
|
|
356
|
-
// must still mutate the state container correctly — the only missing
|
|
357
|
-
// side effect is the transition notification.
|
|
358
|
-
const state = makeState();
|
|
359
|
-
|
|
360
|
-
for (let i = 0; i < 3; i++) {
|
|
361
|
-
await runCircuit({ key: "k", outcome: "failure", state });
|
|
362
|
-
}
|
|
363
|
-
expect(state.consecutiveCompactionFailures).toBe(3);
|
|
364
|
-
expect(state.compactionCircuitOpenUntil).not.toBeNull();
|
|
365
|
-
|
|
366
|
-
await runCircuit({ key: "k", outcome: "success", state });
|
|
367
|
-
expect(state.consecutiveCompactionFailures).toBe(0);
|
|
368
|
-
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
test("pipeline runner applies registered middleware in registration order", async () => {
|
|
372
|
-
// A second plugin registered after the default can observe args/result
|
|
373
|
-
// around the default's behavior. This proves the pipeline composes both
|
|
374
|
-
// middlewares rather than short-circuiting on the default alone.
|
|
375
|
-
const seen: string[] = [];
|
|
376
|
-
registerPlugin({
|
|
377
|
-
manifest: {
|
|
378
|
-
name: "observer",
|
|
379
|
-
version: "0.0.1",
|
|
380
|
-
},
|
|
381
|
-
middleware: {
|
|
382
|
-
circuitBreaker: async (args, next) => {
|
|
383
|
-
seen.push(`before:${args.outcome ?? "query"}`);
|
|
384
|
-
const res = await next(args);
|
|
385
|
-
seen.push(`after:${res.open ? "open" : "closed"}`);
|
|
386
|
-
return res;
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
const state = makeState();
|
|
392
|
-
await runCircuit({ key: "k", outcome: "failure", state });
|
|
393
|
-
await runCircuit({ key: "k", outcome: "failure", state });
|
|
394
|
-
await runCircuit({ key: "k", outcome: "failure", state });
|
|
395
|
-
|
|
396
|
-
expect(seen).toEqual([
|
|
397
|
-
"before:failure",
|
|
398
|
-
"after:closed",
|
|
399
|
-
"before:failure",
|
|
400
|
-
"after:closed",
|
|
401
|
-
"before:failure",
|
|
402
|
-
"after:open",
|
|
403
|
-
]);
|
|
404
|
-
});
|
|
405
|
-
});
|