@vellumai/assistant 0.8.7 → 0.8.8
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/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/docs/plugins.md +75 -79
- package/examples/plugins/echo/README.md +6 -12
- package/examples/plugins/echo/register.ts +0 -41
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
- package/openapi.yaml +3381 -348
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +68 -41
- package/src/__tests__/agent-loop-exit-reason.test.ts +34 -39
- 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__/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__/clawhub-files.test.ts +1 -1
- package/src/__tests__/compaction-pipeline.test.ts +1 -1
- 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 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +313 -1136
- package/src/__tests__/conversation-agent-loop.test.ts +596 -1616
- package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
- 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 +26 -5
- 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 +170 -229
- package/src/__tests__/conversation-runtime-workspace.test.ts +3 -24
- package/src/__tests__/conversation-slash-commands.test.ts +8 -42
- package/src/__tests__/conversation-slash-queue.test.ts +6 -1
- 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-injection.test.ts +6 -1
- package/src/__tests__/cross-provider-web-search.test.ts +214 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
- package/src/__tests__/dm-persistence.test.ts +5 -1
- package/src/__tests__/empty-response-hook.test.ts +304 -0
- package/src/__tests__/feature-flag-test-helpers.ts +2 -2
- package/src/__tests__/gemini-image-service.test.ts +13 -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__/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 +2 -8
- package/src/__tests__/injector-chain.test.ts +106 -270
- package/src/__tests__/injector-disk-pressure.test.ts +3 -12
- package/src/__tests__/injector-document-comments.test.ts +2 -2
- 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-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__/persist-unsendable-image.test.ts +215 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
- package/src/__tests__/pipeline-runner.test.ts +29 -39
- package/src/__tests__/pkb-autoinject.test.ts +2 -5
- package/src/__tests__/plugin-bootstrap.test.ts +13 -28
- package/src/__tests__/plugin-registry.test.ts +0 -27
- package/src/__tests__/plugin-types.test.ts +2 -125
- package/src/__tests__/process-message-display-content.test.ts +6 -2
- 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__/server-history-render.test.ts +314 -1
- package/src/__tests__/skillssh-files.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 +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +6 -3
- package/src/__tests__/web-search-backend-failure.test.ts +166 -0
- package/src/agent/loop.ts +346 -442
- 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 +368 -0
- package/src/avatar/__tests__/avatar-store.test.ts +34 -29
- package/src/cli/commands/__tests__/notifications.test.ts +58 -14
- package/src/cli/commands/notifications.ts +112 -60
- package/src/config/assistant-feature-flags.ts +22 -11
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -20
- 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/document-editor/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +0 -7
- package/src/config/feature-flag-cache.ts +3 -3
- package/src/config/feature-flag-registry.json +35 -3
- 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/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 +122 -0
- package/src/context/token-estimator.ts +23 -0
- package/src/context/tool-result-truncation.ts +0 -23
- package/src/context/window-manager.ts +3 -6
- 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 +605 -153
- package/src/daemon/conversation-agent-loop.ts +281 -760
- 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-runtime-assembly.ts +130 -347
- package/src/daemon/conversation-slash.ts +6 -25
- package/src/daemon/conversation-surfaces.ts +222 -4
- package/src/daemon/conversation-tool-setup.ts +2 -29
- package/src/daemon/conversation.ts +32 -14
- package/src/daemon/external-plugins-bootstrap.ts +9 -10
- 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/shared.ts +156 -84
- package/src/daemon/handlers/skills.ts +39 -10
- package/src/daemon/lifecycle.ts +4 -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/persist-unsendable-image.ts +117 -0
- package/src/daemon/process-message.ts +1 -3
- 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/home/home-greeting-cache.ts +24 -1
- package/src/ipc/gateway-client.test.ts +2 -2
- package/src/ipc/gateway-client.ts +3 -3
- 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-title-service.ts +65 -41
- package/src/memory/db-init.ts +4 -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/jobs-store.ts +33 -0
- package/src/memory/jobs-worker.ts +31 -4
- 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/index.ts +2 -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/infrastructure.ts +16 -0
- package/src/memory/usage-grouped-buckets.ts +6 -1
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -1
- package/src/memory/v2/consolidation-job.ts +1 -1
- package/src/memory/v3/__tests__/health.test.ts +16 -0
- package/src/memory/v3/__tests__/orchestrate.test.ts +45 -9
- package/src/memory/v3/__tests__/provider-blocks.test.ts +13 -0
- package/src/memory/v3/__tests__/router.test.ts +101 -29
- package/src/memory/v3/__tests__/selector.test.ts +93 -27
- package/src/memory/v3/__tests__/shadow-plugin.test.ts +23 -5
- package/src/memory/v3/health.ts +0 -0
- package/src/memory/v3/llm-retry.ts +32 -0
- package/src/memory/v3/orchestrate.ts +26 -14
- package/src/memory/v3/provider-blocks.ts +15 -5
- package/src/memory/v3/router.ts +48 -42
- package/src/memory/v3/selector.ts +57 -42
- package/src/memory/v3/shadow-plugin.ts +47 -15
- package/src/memory/v3/types.ts +8 -0
- 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 +8 -1
- package/src/plugin-api/types.ts +151 -1
- 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 +1 -15
- package/src/plugins/defaults/injectors/register.ts +243 -74
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +91 -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/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/pipeline.ts +6 -18
- package/src/plugins/registry.ts +8 -25
- package/src/plugins/types.ts +43 -474
- package/src/proactive-artifact/aux-message-injector.ts +3 -3
- package/src/proactive-artifact/job.test.ts +7 -12
- package/src/prompts/__tests__/system-prompt.test.ts +36 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +62 -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 +5 -1
- package/src/runtime/agent-wake.ts +2 -2
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
- package/src/runtime/http-router.ts +16 -21
- package/src/runtime/http-types.ts +16 -70
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
- 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__/tts-routes.test.ts +6 -2
- package/src/runtime/routes/app-management-routes.ts +6 -117
- package/src/runtime/routes/app-routes.ts +13 -15
- package/src/runtime/routes/attachment-routes.ts +26 -15
- package/src/runtime/routes/avatar-routes.ts +26 -0
- package/src/runtime/routes/btw-routes.ts +29 -23
- package/src/runtime/routes/consolidation-routes.ts +120 -20
- package/src/runtime/routes/conversation-query-routes.ts +2 -0
- package/src/runtime/routes/conversation-routes.ts +358 -184
- 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/identity-intro-cache.ts +11 -34
- package/src/runtime/routes/identity-routes.ts +208 -17
- package/src/runtime/routes/image-generation-routes.ts +40 -2
- 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 +50 -28
- 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/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/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/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/schedule/schedule-store.ts +20 -1
- package/src/schedule/schedule-usage-store.ts +83 -0
- package/src/schedule/scheduler.ts +12 -5
- 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/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/executor.ts +1 -53
- 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/terminal/safe-env.ts +10 -1
- package/src/tools/ui-surface/definitions.ts +9 -1
- 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/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 +93 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
- 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__/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/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/package.json +0 -15
- 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/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
|
@@ -71,6 +71,8 @@ describe("onboarding template contracts", () => {
|
|
|
71
71
|
|
|
72
72
|
test("offers assistant migration during low-signal first openings", () => {
|
|
73
73
|
expect(bootstrap).toContain("## Assistant migration");
|
|
74
|
+
expect(bootstrap).toContain("onboarding self-introduction");
|
|
75
|
+
expect(bootstrap).toContain("treat it as the real first user turn");
|
|
74
76
|
expect(bootstrap).toContain(
|
|
75
77
|
"If the first real user turn is only a greeting",
|
|
76
78
|
);
|
|
@@ -351,6 +351,23 @@ describe("mapOpenAIError", () => {
|
|
|
351
351
|
expect(msg).toContain("Rate limit");
|
|
352
352
|
});
|
|
353
353
|
|
|
354
|
+
test("maps 402 status to a non-retryable out-of-credits message", () => {
|
|
355
|
+
const msg = mapOpenAIError(new FakeAPIError(402, "payment required"));
|
|
356
|
+
expect(msg).toContain("out of credits");
|
|
357
|
+
expect(msg).not.toContain("Please try again");
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("maps insufficient_quota (reported as 429) to out-of-credits message", () => {
|
|
361
|
+
const msg = mapOpenAIError(
|
|
362
|
+
new FakeAPIError(
|
|
363
|
+
429,
|
|
364
|
+
"You exceeded your current quota, please check your plan and billing details. (insufficient_quota)",
|
|
365
|
+
),
|
|
366
|
+
);
|
|
367
|
+
expect(msg).toContain("out of credits");
|
|
368
|
+
expect(msg).not.toContain("Rate limit");
|
|
369
|
+
});
|
|
370
|
+
|
|
354
371
|
test("maps 500 status to server error message", () => {
|
|
355
372
|
const msg = mapOpenAIError(new FakeAPIError(500, "internal"));
|
|
356
373
|
expect(msg).toContain("temporarily unavailable");
|
|
@@ -105,7 +105,10 @@ mock.module("openai", () => ({
|
|
|
105
105
|
import { FireworksProvider } from "../providers/fireworks/client.js";
|
|
106
106
|
import { MinimaxProvider } from "../providers/minimax/client.js";
|
|
107
107
|
import { OllamaProvider } from "../providers/ollama/client.js";
|
|
108
|
-
import {
|
|
108
|
+
import {
|
|
109
|
+
EMPTY_ASSISTANT_TURN_PLACEHOLDER,
|
|
110
|
+
OpenAIChatCompletionsProvider,
|
|
111
|
+
} from "../providers/openai/chat-completions-provider.js";
|
|
109
112
|
import { OpenAIProvider } from "../providers/openai/client.js";
|
|
110
113
|
import { OpenRouterProvider } from "../providers/openrouter/client.js";
|
|
111
114
|
|
|
@@ -1584,6 +1587,33 @@ describe("OpenRouterProvider reasoning", () => {
|
|
|
1584
1587
|
expect(lastCreateParams).not.toHaveProperty("usageAttributionHeaders");
|
|
1585
1588
|
});
|
|
1586
1589
|
|
|
1590
|
+
test("backfills placeholder content for a reasoning-only assistant turn", async () => {
|
|
1591
|
+
const provider = new OpenRouterProvider("or-key", "deepseek/deepseek-chat");
|
|
1592
|
+
await provider.sendMessage([
|
|
1593
|
+
userMsg("question"),
|
|
1594
|
+
{
|
|
1595
|
+
role: "assistant",
|
|
1596
|
+
content: [
|
|
1597
|
+
{ type: "thinking", thinking: "truncated reasoning", signature: "" },
|
|
1598
|
+
],
|
|
1599
|
+
},
|
|
1600
|
+
]);
|
|
1601
|
+
|
|
1602
|
+
const sent = lastCreateParams!.messages as Array<{
|
|
1603
|
+
role: string;
|
|
1604
|
+
content: string | null;
|
|
1605
|
+
reasoning?: string;
|
|
1606
|
+
tool_calls?: unknown;
|
|
1607
|
+
}>;
|
|
1608
|
+
const assistantMsg = sent.find((m) => m.role === "assistant")!;
|
|
1609
|
+
// DeepSeek via OpenRouter rejects an assistant message with neither content
|
|
1610
|
+
// nor tool_calls, so the reasoning-only turn is backfilled with the sentinel
|
|
1611
|
+
// while the reasoning itself travels in the separate `reasoning` field.
|
|
1612
|
+
expect(assistantMsg.content).toBe(EMPTY_ASSISTANT_TURN_PLACEHOLDER);
|
|
1613
|
+
expect(assistantMsg.tool_calls).toBeUndefined();
|
|
1614
|
+
expect(assistantMsg.reasoning).toBe("truncated reasoning");
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1587
1617
|
test("RetryProvider + OpenRouterProvider enables thinking end-to-end", async () => {
|
|
1588
1618
|
const provider = new OpenRouterProvider("or-key", "x-ai/grok-4");
|
|
1589
1619
|
const retry = new RetryProvider(provider);
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for the image-too-large persistence path (JARVIS-1037).
|
|
3
|
+
*
|
|
4
|
+
* An image the provider can never accept — over the per-side pixel cap or the
|
|
5
|
+
* per-image byte cap, and not shrinkable on this host — must be durably swapped
|
|
6
|
+
* for a text note in its stored message. If it stays in the stored content,
|
|
7
|
+
* every later turn rehydrates it from the DB and the model reports seeing both
|
|
8
|
+
* the rejected image and any smaller re-upload. `persistUnsendableImageDowngrades`
|
|
9
|
+
* makes the swap durable so the rejected upload cannot resurface.
|
|
10
|
+
*
|
|
11
|
+
* Uses the real SQLite DB wired up via `test-preload.ts` (per-file temp
|
|
12
|
+
* workspace).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
16
|
+
|
|
17
|
+
import { persistUnsendableImageDowngrades } from "../daemon/persist-unsendable-image.js";
|
|
18
|
+
import {
|
|
19
|
+
addMessage,
|
|
20
|
+
createConversation,
|
|
21
|
+
getMessages,
|
|
22
|
+
} from "../memory/conversation-crud.js";
|
|
23
|
+
import { getDb } from "../memory/db-connection.js";
|
|
24
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
25
|
+
import type { ContentBlock } from "../providers/types.js";
|
|
26
|
+
|
|
27
|
+
initializeDb();
|
|
28
|
+
|
|
29
|
+
function resetTables(): void {
|
|
30
|
+
const db = getDb();
|
|
31
|
+
db.run("DELETE FROM message_attachments");
|
|
32
|
+
db.run("DELETE FROM attachments");
|
|
33
|
+
db.run("DELETE FROM memory_segments");
|
|
34
|
+
db.run("DELETE FROM memory_embeddings");
|
|
35
|
+
db.run("DELETE FROM messages");
|
|
36
|
+
db.run("DELETE FROM conversations");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build a minimal PNG whose IHDR declares the given dimensions. Only the
|
|
41
|
+
* 8-byte signature and the width/height fields (read by `parseImageDimensions`)
|
|
42
|
+
* need to be correct; the rest is padding. `optimizeImageForTransport` cannot
|
|
43
|
+
* downscale this off macOS (no `sips`), so it stays a no-op — exactly the
|
|
44
|
+
* host condition that produces an unsendable stored image.
|
|
45
|
+
*/
|
|
46
|
+
function makePngBase64(width: number, height: number, padBytes = 0): string {
|
|
47
|
+
const header = Buffer.from(
|
|
48
|
+
Uint8Array.from([
|
|
49
|
+
0x89,
|
|
50
|
+
0x50,
|
|
51
|
+
0x4e,
|
|
52
|
+
0x47,
|
|
53
|
+
0x0d,
|
|
54
|
+
0x0a,
|
|
55
|
+
0x1a,
|
|
56
|
+
0x0a, // PNG signature
|
|
57
|
+
0x00,
|
|
58
|
+
0x00,
|
|
59
|
+
0x00,
|
|
60
|
+
0x0d, // IHDR length (13)
|
|
61
|
+
0x49,
|
|
62
|
+
0x48,
|
|
63
|
+
0x44,
|
|
64
|
+
0x52, // "IHDR"
|
|
65
|
+
(width >>> 24) & 0xff,
|
|
66
|
+
(width >>> 16) & 0xff,
|
|
67
|
+
(width >>> 8) & 0xff,
|
|
68
|
+
width & 0xff,
|
|
69
|
+
(height >>> 24) & 0xff,
|
|
70
|
+
(height >>> 16) & 0xff,
|
|
71
|
+
(height >>> 8) & 0xff,
|
|
72
|
+
height & 0xff,
|
|
73
|
+
0x08,
|
|
74
|
+
0x06,
|
|
75
|
+
0x00,
|
|
76
|
+
0x00,
|
|
77
|
+
0x00, // bit depth / color type / etc.
|
|
78
|
+
]),
|
|
79
|
+
).toString("base64");
|
|
80
|
+
return padBytes > 0 ? header + "A".repeat(padBytes) : header;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function imageBlock(data: string): ContentBlock {
|
|
84
|
+
return {
|
|
85
|
+
type: "image",
|
|
86
|
+
source: { type: "base64", media_type: "image/png", data },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function storedContent(conversationId: string): ContentBlock[][] {
|
|
91
|
+
return getMessages(conversationId).map(
|
|
92
|
+
(row) => JSON.parse(row.content) as ContentBlock[],
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const PROVIDER_MAX_IMAGE_DIMENSION = 8000;
|
|
97
|
+
|
|
98
|
+
describe("persistUnsendableImageDowngrades", () => {
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
resetTables();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/** A stored image past the provider pixel cap is swapped for a text note. */
|
|
104
|
+
test("replaces an oversized image block with a text note", async () => {
|
|
105
|
+
// GIVEN a message holding text plus an image past the pixel cap
|
|
106
|
+
const conv = createConversation();
|
|
107
|
+
const oversized = makePngBase64(PROVIDER_MAX_IMAGE_DIMENSION + 1000, 6000);
|
|
108
|
+
await addMessage(
|
|
109
|
+
conv.id,
|
|
110
|
+
"user",
|
|
111
|
+
JSON.stringify([
|
|
112
|
+
{ type: "text", text: "look at this" },
|
|
113
|
+
imageBlock(oversized),
|
|
114
|
+
]),
|
|
115
|
+
{ skipIndexing: true },
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// WHEN the downgrade is persisted
|
|
119
|
+
const rewritten = persistUnsendableImageDowngrades(conv.id);
|
|
120
|
+
|
|
121
|
+
// THEN one message is rewritten with no image block left
|
|
122
|
+
expect(rewritten).toBe(1);
|
|
123
|
+
const [content] = storedContent(conv.id);
|
|
124
|
+
expect(content.some((b) => b.type === "image")).toBe(false);
|
|
125
|
+
// AND the original text is preserved alongside the substituted note
|
|
126
|
+
expect(content.filter((b) => b.type === "text")).toHaveLength(2);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/** The JARVIS-1037 scenario: the rejected original must not resurface next
|
|
130
|
+
* to a valid re-upload. */
|
|
131
|
+
test("re-uploaded smaller image survives while the rejected original is removed", async () => {
|
|
132
|
+
// GIVEN turn 1 contains an oversized upload that was rejected
|
|
133
|
+
const conv = createConversation();
|
|
134
|
+
await addMessage(
|
|
135
|
+
conv.id,
|
|
136
|
+
"user",
|
|
137
|
+
JSON.stringify([imageBlock(makePngBase64(12000, 9000))]),
|
|
138
|
+
{ skipIndexing: true },
|
|
139
|
+
);
|
|
140
|
+
// AND turn 2 contains a properly sized re-upload
|
|
141
|
+
await addMessage(
|
|
142
|
+
conv.id,
|
|
143
|
+
"user",
|
|
144
|
+
JSON.stringify([imageBlock(makePngBase64(800, 600))]),
|
|
145
|
+
{ skipIndexing: true },
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// WHEN the downgrade is persisted
|
|
149
|
+
const rewritten = persistUnsendableImageDowngrades(conv.id);
|
|
150
|
+
|
|
151
|
+
// THEN only the rejected original is removed
|
|
152
|
+
expect(rewritten).toBe(1);
|
|
153
|
+
const [first, second] = storedContent(conv.id);
|
|
154
|
+
expect(first.some((b) => b.type === "image")).toBe(false);
|
|
155
|
+
// AND the valid re-upload is left intact
|
|
156
|
+
expect(second.some((b) => b.type === "image")).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
/** Sendable images are never disturbed by the recovery path. */
|
|
160
|
+
test("leaves a normally-sized image untouched", async () => {
|
|
161
|
+
// GIVEN a message with an image well within provider limits
|
|
162
|
+
const conv = createConversation();
|
|
163
|
+
await addMessage(
|
|
164
|
+
conv.id,
|
|
165
|
+
"user",
|
|
166
|
+
JSON.stringify([imageBlock(makePngBase64(1024, 768))]),
|
|
167
|
+
{ skipIndexing: true },
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// WHEN the downgrade is persisted
|
|
171
|
+
const rewritten = persistUnsendableImageDowngrades(conv.id);
|
|
172
|
+
|
|
173
|
+
// THEN nothing is rewritten and the image remains
|
|
174
|
+
expect(rewritten).toBe(0);
|
|
175
|
+
const [content] = storedContent(conv.id);
|
|
176
|
+
expect(content.some((b) => b.type === "image")).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/** The byte-size cap is enforced independently of pixel dimensions. */
|
|
180
|
+
test("removes an image whose payload exceeds the per-image byte cap", async () => {
|
|
181
|
+
// GIVEN an image within the pixel cap but with a payload over 5 MB
|
|
182
|
+
const conv = createConversation();
|
|
183
|
+
const huge = makePngBase64(1000, 1000, 6 * 1024 * 1024);
|
|
184
|
+
await addMessage(conv.id, "user", JSON.stringify([imageBlock(huge)]), {
|
|
185
|
+
skipIndexing: true,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// WHEN the downgrade is persisted
|
|
189
|
+
const rewritten = persistUnsendableImageDowngrades(conv.id);
|
|
190
|
+
|
|
191
|
+
// THEN the oversized-payload image is removed
|
|
192
|
+
expect(rewritten).toBe(1);
|
|
193
|
+
const [content] = storedContent(conv.id);
|
|
194
|
+
expect(content.some((b) => b.type === "image")).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
/** Re-running after a rewrite is a safe no-op (no image blocks remain). */
|
|
198
|
+
test("is idempotent — a second run rewrites nothing", async () => {
|
|
199
|
+
// GIVEN a conversation whose oversized image has already been downgraded
|
|
200
|
+
const conv = createConversation();
|
|
201
|
+
await addMessage(
|
|
202
|
+
conv.id,
|
|
203
|
+
"user",
|
|
204
|
+
JSON.stringify([imageBlock(makePngBase64(10000, 10000))]),
|
|
205
|
+
{ skipIndexing: true },
|
|
206
|
+
);
|
|
207
|
+
expect(persistUnsendableImageDowngrades(conv.id)).toBe(1);
|
|
208
|
+
|
|
209
|
+
// WHEN the downgrade runs a second time
|
|
210
|
+
const secondRun = persistUnsendableImageDowngrades(conv.id);
|
|
211
|
+
|
|
212
|
+
// THEN nothing further is rewritten
|
|
213
|
+
expect(secondRun).toBe(0);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -170,12 +170,12 @@ describe("runPipeline — error propagation", () => {
|
|
|
170
170
|
|
|
171
171
|
await expect(
|
|
172
172
|
runPipeline(
|
|
173
|
-
"
|
|
173
|
+
"circuitBreaker",
|
|
174
174
|
[thrower],
|
|
175
175
|
terminal,
|
|
176
176
|
{ value: 1 },
|
|
177
177
|
makeCtx(),
|
|
178
|
-
DEFAULT_TIMEOUTS.
|
|
178
|
+
DEFAULT_TIMEOUTS.circuitBreaker,
|
|
179
179
|
),
|
|
180
180
|
).rejects.toBeInstanceOf(Boom);
|
|
181
181
|
});
|
|
@@ -187,12 +187,12 @@ describe("runPipeline — error propagation", () => {
|
|
|
187
187
|
|
|
188
188
|
await expect(
|
|
189
189
|
runPipeline(
|
|
190
|
-
"
|
|
190
|
+
"circuitBreaker",
|
|
191
191
|
[],
|
|
192
192
|
terminal,
|
|
193
193
|
{ value: 1 },
|
|
194
194
|
makeCtx(),
|
|
195
|
-
DEFAULT_TIMEOUTS.
|
|
195
|
+
DEFAULT_TIMEOUTS.circuitBreaker,
|
|
196
196
|
),
|
|
197
197
|
).rejects.toBeInstanceOf(TypeError);
|
|
198
198
|
});
|
|
@@ -210,7 +210,7 @@ describe("runPipeline — timeout", () => {
|
|
|
210
210
|
let caught: unknown;
|
|
211
211
|
try {
|
|
212
212
|
await runPipeline(
|
|
213
|
-
"
|
|
213
|
+
"compaction",
|
|
214
214
|
[sleeper],
|
|
215
215
|
terminal,
|
|
216
216
|
{ value: 1 },
|
|
@@ -223,10 +223,10 @@ describe("runPipeline — timeout", () => {
|
|
|
223
223
|
|
|
224
224
|
expect(caught).toBeInstanceOf(PluginTimeoutError);
|
|
225
225
|
const tErr = caught as PluginTimeoutError;
|
|
226
|
-
expect(tErr.pipeline).toBe("
|
|
226
|
+
expect(tErr.pipeline).toBe("compaction");
|
|
227
227
|
expect(tErr.pluginName).toBe("slow-plugin");
|
|
228
228
|
expect(tErr.elapsedMs).toBeGreaterThanOrEqual(0);
|
|
229
|
-
expect(tErr.message).toContain("
|
|
229
|
+
expect(tErr.message).toContain("compaction");
|
|
230
230
|
expect(tErr.message).toContain("slow-plugin");
|
|
231
231
|
});
|
|
232
232
|
|
|
@@ -235,32 +235,32 @@ describe("runPipeline — timeout", () => {
|
|
|
235
235
|
value: args.value,
|
|
236
236
|
});
|
|
237
237
|
const result = await runPipeline(
|
|
238
|
-
"
|
|
238
|
+
"compaction",
|
|
239
239
|
[],
|
|
240
240
|
terminal,
|
|
241
241
|
{ value: 42 },
|
|
242
242
|
makeCtx(),
|
|
243
|
-
DEFAULT_TIMEOUTS.
|
|
243
|
+
DEFAULT_TIMEOUTS.compaction,
|
|
244
244
|
);
|
|
245
245
|
expect(result).toEqual({ value: 42 });
|
|
246
246
|
});
|
|
247
247
|
|
|
248
248
|
test("null timeout skips the race entirely", async () => {
|
|
249
|
-
//
|
|
250
|
-
// timer. We verify by completing after an artificial 30ms wait
|
|
251
|
-
// confirming success without interference.
|
|
249
|
+
// compaction has DEFAULT_TIMEOUTS.compaction === null — runner must
|
|
250
|
+
// not arm a timer. We verify by completing after an artificial 30ms wait
|
|
251
|
+
// and confirming success without interference.
|
|
252
252
|
const sleeper: Middleware<Args, Result> = async (args, next) =>
|
|
253
253
|
new Promise<Result>((resolve) => {
|
|
254
254
|
setTimeout(() => resolve(next(args)), 30);
|
|
255
255
|
});
|
|
256
256
|
const terminal = async (_args: Args): Promise<Result> => ({ value: 1 });
|
|
257
257
|
const result = await runPipeline(
|
|
258
|
-
"
|
|
258
|
+
"compaction",
|
|
259
259
|
[sleeper],
|
|
260
260
|
terminal,
|
|
261
261
|
{ value: 0 },
|
|
262
262
|
makeCtx(),
|
|
263
|
-
DEFAULT_TIMEOUTS.
|
|
263
|
+
DEFAULT_TIMEOUTS.compaction,
|
|
264
264
|
);
|
|
265
265
|
expect(result).toEqual({ value: 1 });
|
|
266
266
|
});
|
|
@@ -356,7 +356,7 @@ describe("runPipeline — timeout aborts linked signal", () => {
|
|
|
356
356
|
});
|
|
357
357
|
|
|
358
358
|
test("args without an AbortSignal property is passed through unchanged", async () => {
|
|
359
|
-
// Sanity — pipelines that don't carry a signal (
|
|
359
|
+
// Sanity — pipelines that don't carry a signal (circuitBreaker)
|
|
360
360
|
// see identical args identity as before the abort-linking change.
|
|
361
361
|
const args: Args = { value: 42 };
|
|
362
362
|
let seen: Args | undefined;
|
|
@@ -365,12 +365,12 @@ describe("runPipeline — timeout aborts linked signal", () => {
|
|
|
365
365
|
return { value: innerArgs.value };
|
|
366
366
|
};
|
|
367
367
|
await runPipeline(
|
|
368
|
-
"
|
|
368
|
+
"circuitBreaker",
|
|
369
369
|
[],
|
|
370
370
|
terminal,
|
|
371
371
|
args,
|
|
372
372
|
makeCtx(),
|
|
373
|
-
DEFAULT_TIMEOUTS.
|
|
373
|
+
DEFAULT_TIMEOUTS.circuitBreaker,
|
|
374
374
|
);
|
|
375
375
|
expect(seen).toBe(args);
|
|
376
376
|
});
|
|
@@ -428,19 +428,19 @@ describe("runPipeline — structured log record", () => {
|
|
|
428
428
|
|
|
429
429
|
await expect(
|
|
430
430
|
runPipeline(
|
|
431
|
-
"
|
|
431
|
+
"circuitBreaker",
|
|
432
432
|
[thrower],
|
|
433
433
|
terminal,
|
|
434
434
|
{ value: 1 },
|
|
435
435
|
makeCtx({ pluginName: "noisy-plugin" }),
|
|
436
|
-
DEFAULT_TIMEOUTS.
|
|
436
|
+
DEFAULT_TIMEOUTS.circuitBreaker,
|
|
437
437
|
),
|
|
438
438
|
).rejects.toBeInstanceOf(Boom);
|
|
439
439
|
|
|
440
440
|
expect(fakeLogger.calls.length).toBe(1);
|
|
441
441
|
const [record] = fakeLogger.calls[0]!;
|
|
442
442
|
expect(record.outcome).toBe("error");
|
|
443
|
-
expect(record.pipeline).toBe("
|
|
443
|
+
expect(record.pipeline).toBe("circuitBreaker");
|
|
444
444
|
expect(record.errorName).toBe("BoomError");
|
|
445
445
|
expect(record.errorMessage).toBe("kaboom");
|
|
446
446
|
expect(typeof record.errorStack).toBe("string");
|
|
@@ -456,7 +456,7 @@ describe("runPipeline — structured log record", () => {
|
|
|
456
456
|
|
|
457
457
|
await expect(
|
|
458
458
|
runPipeline(
|
|
459
|
-
"
|
|
459
|
+
"circuitBreaker",
|
|
460
460
|
[sleeper],
|
|
461
461
|
terminal,
|
|
462
462
|
{ value: 1 },
|
|
@@ -468,9 +468,9 @@ describe("runPipeline — structured log record", () => {
|
|
|
468
468
|
expect(fakeLogger.calls.length).toBe(1);
|
|
469
469
|
const [record] = fakeLogger.calls[0]!;
|
|
470
470
|
expect(record.outcome).toBe("timeout");
|
|
471
|
-
expect(record.pipeline).toBe("
|
|
471
|
+
expect(record.pipeline).toBe("circuitBreaker");
|
|
472
472
|
expect(record.errorName).toBe("PluginTimeoutError");
|
|
473
|
-
expect(String(record.errorMessage)).toContain("
|
|
473
|
+
expect(String(record.errorMessage)).toContain("circuitBreaker");
|
|
474
474
|
expect(String(record.errorMessage)).toContain("slow-plugin");
|
|
475
475
|
expect(record.timeoutMs).toBe(15);
|
|
476
476
|
expect(record.pluginName).toBe("slow-plugin");
|
|
@@ -481,17 +481,17 @@ describe("runPipeline — structured log record", () => {
|
|
|
481
481
|
value: args.value,
|
|
482
482
|
});
|
|
483
483
|
await runPipeline(
|
|
484
|
-
"
|
|
484
|
+
"compaction",
|
|
485
485
|
[],
|
|
486
486
|
terminal,
|
|
487
487
|
{ value: 5 },
|
|
488
488
|
makeCtx(),
|
|
489
|
-
DEFAULT_TIMEOUTS.
|
|
489
|
+
DEFAULT_TIMEOUTS.compaction,
|
|
490
490
|
);
|
|
491
491
|
|
|
492
492
|
expect(fakeLogger.calls.length).toBe(1);
|
|
493
493
|
const [record] = fakeLogger.calls[0]!;
|
|
494
|
-
expect(record.pipeline).toBe("
|
|
494
|
+
expect(record.pipeline).toBe("compaction");
|
|
495
495
|
expect(record.outcome).toBe("success");
|
|
496
496
|
expect(record.timeoutMs).toBeUndefined();
|
|
497
497
|
});
|
|
@@ -506,7 +506,7 @@ describe("runPipeline — structured log record", () => {
|
|
|
506
506
|
logger: fakeLogger,
|
|
507
507
|
} as TurnContext;
|
|
508
508
|
await runPipeline(
|
|
509
|
-
"
|
|
509
|
+
"circuitBreaker",
|
|
510
510
|
[],
|
|
511
511
|
terminal,
|
|
512
512
|
{ value: 0 },
|
|
@@ -531,12 +531,12 @@ describe("runPipeline — structured log record", () => {
|
|
|
531
531
|
value: args.value,
|
|
532
532
|
});
|
|
533
533
|
await runPipeline(
|
|
534
|
-
"
|
|
534
|
+
"compaction",
|
|
535
535
|
[a, b, c],
|
|
536
536
|
terminal,
|
|
537
537
|
{ value: 0 },
|
|
538
538
|
makeCtx(),
|
|
539
|
-
DEFAULT_TIMEOUTS.
|
|
539
|
+
DEFAULT_TIMEOUTS.compaction,
|
|
540
540
|
);
|
|
541
541
|
const [record] = fakeLogger.calls[0]!;
|
|
542
542
|
expect(record.chain).toEqual(["outerA", "middleB", "innerC"]);
|
|
@@ -546,18 +546,8 @@ describe("runPipeline — structured log record", () => {
|
|
|
546
546
|
describe("DEFAULT_TIMEOUTS", () => {
|
|
547
547
|
test("matches the design-doc table exactly", () => {
|
|
548
548
|
expect(DEFAULT_TIMEOUTS).toEqual({
|
|
549
|
-
turn: null,
|
|
550
|
-
llmCall: null,
|
|
551
|
-
toolExecute: null,
|
|
552
|
-
memoryRetrieval: null,
|
|
553
|
-
tokenEstimate: null,
|
|
554
549
|
compaction: null,
|
|
555
550
|
overflowReduce: null,
|
|
556
|
-
persistence: null,
|
|
557
|
-
titleGenerate: null,
|
|
558
|
-
toolResultTruncate: null,
|
|
559
|
-
emptyResponse: null,
|
|
560
|
-
toolError: null,
|
|
561
551
|
circuitBreaker: null,
|
|
562
552
|
});
|
|
563
553
|
});
|
|
@@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
6
6
|
import {
|
|
7
7
|
getPkbAutoInjectList,
|
|
8
8
|
readAutoinjectList,
|
|
9
|
-
} from "../
|
|
9
|
+
} from "../memory/pkb/autoinject.js";
|
|
10
10
|
|
|
11
11
|
const PKB_DEFAULT_FILES = [
|
|
12
12
|
"INDEX.md",
|
|
@@ -80,10 +80,7 @@ describe("readAutoinjectList", () => {
|
|
|
80
80
|
"INDEX.md\ncustom-topic.md\n",
|
|
81
81
|
"utf-8",
|
|
82
82
|
);
|
|
83
|
-
expect(readAutoinjectList(pkbDir)).toEqual([
|
|
84
|
-
"INDEX.md",
|
|
85
|
-
"custom-topic.md",
|
|
86
|
-
]);
|
|
83
|
+
expect(readAutoinjectList(pkbDir)).toEqual(["INDEX.md", "custom-topic.md"]);
|
|
87
84
|
});
|
|
88
85
|
|
|
89
86
|
test("strips blank lines and whitespace", () => {
|
|
@@ -38,7 +38,6 @@ import { RiskLevel } from "../permissions/types.js";
|
|
|
38
38
|
import { registerDefaultPlugins } from "../plugins/defaults/index.js";
|
|
39
39
|
import {
|
|
40
40
|
closeRegistration,
|
|
41
|
-
getInjectors,
|
|
42
41
|
getMiddlewaresFor,
|
|
43
42
|
getRegisteredPlugins,
|
|
44
43
|
registerPlugin,
|
|
@@ -342,8 +341,8 @@ describe("plugin bootstrap", () => {
|
|
|
342
341
|
// THEN the defaults are still registered, ahead of the user plugin, so the
|
|
343
342
|
// middleware onion order is unchanged
|
|
344
343
|
const names = getRegisteredPlugins().map((p) => p.manifest.name);
|
|
345
|
-
expect(names).toContain("default-
|
|
346
|
-
expect(names.indexOf("default-
|
|
344
|
+
expect(names).toContain("default-compaction");
|
|
345
|
+
expect(names.indexOf("default-compaction")).toBeLessThan(
|
|
347
346
|
names.indexOf("user-after-defaults"),
|
|
348
347
|
);
|
|
349
348
|
});
|
|
@@ -486,32 +485,22 @@ describe("plugin bootstrap", () => {
|
|
|
486
485
|
expect(initFired).toBe(false);
|
|
487
486
|
});
|
|
488
487
|
|
|
489
|
-
test("requiresFlag disabled: plugin middleware
|
|
488
|
+
test("requiresFlag disabled: plugin middleware is dropped from the registry", async () => {
|
|
490
489
|
// Regression: prior to the unregisterPlugin() call on the flag-gated skip
|
|
491
|
-
// path, `getMiddlewaresFor()`
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
// depended on.
|
|
490
|
+
// path, `getMiddlewaresFor()` iterated over every entry in
|
|
491
|
+
// `registeredPlugins` — so a gated-off plugin's middleware still ran on
|
|
492
|
+
// every pipeline invocation even though `init()` had never fired to set up
|
|
493
|
+
// the state it depended on.
|
|
496
494
|
setOverridesForTesting({ "plugin-middleware-disabled": false });
|
|
497
495
|
|
|
498
|
-
const gatedMiddleware: PipelineMiddlewareMap["
|
|
496
|
+
const gatedMiddleware: PipelineMiddlewareMap["compaction"] = async (
|
|
499
497
|
args,
|
|
500
498
|
next,
|
|
501
499
|
) => next(args);
|
|
502
500
|
const plugin = buildPlugin(
|
|
503
501
|
"gated-middleware",
|
|
504
502
|
{
|
|
505
|
-
middleware: {
|
|
506
|
-
injectors: [
|
|
507
|
-
{
|
|
508
|
-
name: "gated-middleware-injector",
|
|
509
|
-
order: 100,
|
|
510
|
-
async produce() {
|
|
511
|
-
return null;
|
|
512
|
-
},
|
|
513
|
-
},
|
|
514
|
-
],
|
|
503
|
+
middleware: { compaction: gatedMiddleware },
|
|
515
504
|
},
|
|
516
505
|
{ requiresFlag: ["plugin-middleware-disabled"] },
|
|
517
506
|
);
|
|
@@ -519,14 +508,10 @@ describe("plugin bootstrap", () => {
|
|
|
519
508
|
|
|
520
509
|
await bootstrapPlugins();
|
|
521
510
|
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
expect(getMiddlewaresFor("llmCall")).not.toContain(gatedMiddleware);
|
|
527
|
-
expect(
|
|
528
|
-
getInjectors().some((i) => i.name === "gated-middleware-injector"),
|
|
529
|
-
).toBe(false);
|
|
511
|
+
// The middleware slot must not expose the flag-gated plugin's contribution.
|
|
512
|
+
// The default plugins also contribute compaction middleware, so we key on
|
|
513
|
+
// identity rather than asserting an empty list.
|
|
514
|
+
expect(getMiddlewaresFor("compaction")).not.toContain(gatedMiddleware);
|
|
530
515
|
});
|
|
531
516
|
|
|
532
517
|
test("requiresFlag disabled: no shutdown hook entry installed for the skipped plugin", async () => {
|
|
@@ -10,7 +10,6 @@ import { beforeEach, describe, expect, test } from "bun:test";
|
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
closeRegistration,
|
|
13
|
-
getInjectors,
|
|
14
13
|
getMiddlewaresFor,
|
|
15
14
|
getRegisteredPlugins,
|
|
16
15
|
registerPlugin,
|
|
@@ -19,7 +18,6 @@ import {
|
|
|
19
18
|
import {
|
|
20
19
|
type CompactionArgs,
|
|
21
20
|
type CompactionResult,
|
|
22
|
-
type Injector,
|
|
23
21
|
type Middleware,
|
|
24
22
|
type Plugin,
|
|
25
23
|
PluginExecutionError,
|
|
@@ -149,31 +147,6 @@ describe("plugin registry", () => {
|
|
|
149
147
|
expect(() => registerPlugin(bad)).toThrow(/manifest\.version is required/);
|
|
150
148
|
});
|
|
151
149
|
|
|
152
|
-
test("getInjectors returns injectors sorted by order ascending", () => {
|
|
153
|
-
const high: Injector = {
|
|
154
|
-
name: "high-order",
|
|
155
|
-
order: 20,
|
|
156
|
-
async produce() {
|
|
157
|
-
return null;
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
const low: Injector = {
|
|
161
|
-
name: "low-order",
|
|
162
|
-
order: 10,
|
|
163
|
-
async produce() {
|
|
164
|
-
return null;
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// Register the higher-order plugin first so registration order alone
|
|
169
|
-
// would produce the wrong sequence — the test proves sort-by-order wins.
|
|
170
|
-
registerPlugin(buildPlugin("high", { injectors: [high] }));
|
|
171
|
-
registerPlugin(buildPlugin("low", { injectors: [low] }));
|
|
172
|
-
|
|
173
|
-
const injectors = getInjectors();
|
|
174
|
-
expect(injectors.map((i) => i.name)).toEqual(["low-order", "high-order"]);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
150
|
test("getMiddlewaresFor returns middleware in registration order", () => {
|
|
178
151
|
const firstMw: Middleware<CompactionArgs, CompactionResult> = async (
|
|
179
152
|
args,
|