@vellumai/assistant 0.8.8 → 0.8.9-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +6 -6
- package/bun.lock +2 -2
- package/examples/plugins/echo/README.md +61 -60
- package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
- package/examples/plugins/echo/hooks/stop.ts +16 -0
- package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
- package/examples/plugins/echo/package.json +1 -2
- package/examples/plugins/echo/src/emit.ts +19 -0
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
- package/openapi.yaml +235 -6
- package/package.json +2 -2
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
- package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
- package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
- package/src/__tests__/agent-loop-thinking.test.ts +36 -20
- package/src/__tests__/agent-loop.test.ts +441 -96
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
- package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
- package/src/__tests__/anthropic-provider.test.ts +1 -1
- package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +132 -0
- package/src/__tests__/approval-cascade.test.ts +5 -4
- package/src/__tests__/approval-routes-http.test.ts +4 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +1 -1
- package/src/__tests__/channel-approvals.test.ts +1 -1
- package/src/__tests__/compaction-circuit.test.ts +258 -0
- package/src/__tests__/compaction-direct.test.ts +132 -0
- package/src/__tests__/compaction-events.test.ts +5 -5
- package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
- package/src/__tests__/context-overflow-reducer.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +142 -218
- package/src/__tests__/conversation-agent-loop.test.ts +297 -586
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
- package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
- package/src/__tests__/conversation-process-callsite.test.ts +14 -14
- package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
- package/src/__tests__/conversation-queue.test.ts +9 -9
- package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
- package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
- package/src/__tests__/conversation-slash-queue.test.ts +6 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
- package/src/__tests__/conversation-speed-override.test.ts +10 -9
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
- package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/dynamic-page-surface.test.ts +31 -0
- package/src/__tests__/empty-response-hook.test.ts +1 -1
- package/src/__tests__/file-write-tool.test.ts +63 -0
- package/src/__tests__/gateway-only-guard.test.ts +12 -2
- 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__/history-repair-hook.test.ts +1 -1
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/inference-profile-reaper.test.ts +62 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
- package/src/__tests__/injector-background-turn.test.ts +13 -23
- package/src/__tests__/injector-chain.test.ts +268 -44
- package/src/__tests__/injector-disk-pressure.test.ts +210 -52
- package/src/__tests__/injector-document-comments.test.ts +97 -114
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
- package/src/__tests__/injector-v3-suppression.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
- package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +131 -26
- package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
- package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
- package/src/__tests__/plugin-api-shim.test.ts +6 -9
- package/src/__tests__/plugin-bootstrap.test.ts +12 -23
- package/src/__tests__/plugin-registry.test.ts +3 -49
- package/src/__tests__/plugin-types.test.ts +0 -70
- package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
- package/src/__tests__/steer-tool-repair.test.ts +1 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-detail.test.ts +25 -7
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
- package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
- package/src/__tests__/subagent-manager-notify.test.ts +1 -3
- package/src/__tests__/subagent-notify-parent.test.ts +1 -3
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +1 -1
- package/src/__tests__/tool-error-hook.test.ts +1 -1
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +54 -286
- package/src/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 +106 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +280 -0
- package/src/acp/auto-install.ts +232 -0
- package/src/acp/client-handler.ts +31 -0
- package/src/acp/feature-gate.test.ts +48 -0
- package/src/acp/feature-gate.ts +34 -0
- package/src/acp/prepare-agent-env.ts +80 -27
- package/src/acp/resolve-agent.test.ts +225 -9
- package/src/acp/resolve-agent.ts +122 -17
- package/src/acp/resume-hint.ts +23 -0
- package/src/acp/session-manager.ts +507 -73
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +414 -248
- package/src/api/responses/conversation-message.ts +14 -1
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
- package/src/cli/commands/plugins.ts +43 -37
- package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
- package/src/cli/lib/install-from-github.ts +464 -55
- package/src/cli/lib/plugin-catalog-cache.ts +84 -0
- package/src/cli/lib/plugin-details.ts +409 -0
- package/src/cli/lib/plugin-marketplace.ts +197 -0
- package/src/cli/lib/search-plugins.ts +195 -29
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +10 -0
- package/src/config/acp-defaults.ts +6 -0
- package/src/config/bundled-skills/acp/SKILL.md +85 -33
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
- package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-tool-registry.ts +4 -0
- package/src/config/call-site-defaults.ts +0 -2
- package/src/config/feature-flag-registry.json +15 -6
- package/src/config/schemas/call-site-catalog.ts +0 -14
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +0 -2
- package/src/context/compactor.ts +22 -4
- package/src/context/strip-injections.ts +8 -2
- package/src/context/window-manager.ts +27 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
- package/src/daemon/conversation-agent-loop.ts +175 -1055
- package/src/daemon/conversation-lifecycle.ts +11 -255
- package/src/daemon/conversation-process.ts +8 -136
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +293 -392
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +24 -8
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +404 -57
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/external-plugins-bootstrap.ts +14 -19
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-proxy-preactivation.ts +1 -3
- package/src/daemon/lifecycle.ts +21 -7
- package/src/daemon/server.ts +2 -0
- package/src/daemon/wake-conversation-ops.ts +269 -0
- package/src/embedded/plugin-api.ts +2 -2
- package/src/export/__tests__/transcript-formatter.test.ts +5 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
- package/src/heartbeat/heartbeat-run-store.ts +23 -1
- package/src/heartbeat/heartbeat-service.ts +26 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
- package/src/ipc/skill-routes/memory.ts +4 -2
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
- package/src/memory/conversation-crud.ts +29 -19
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/db-init.ts +2 -0
- package/src/memory/job-handlers/conversation-starters.ts +13 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
- package/src/memory/jobs-worker.ts +25 -1
- package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
- package/src/memory/v2/consolidation-job.ts +13 -4
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +6 -5
- package/src/plugin-api/types.ts +75 -0
- package/src/plugins/defaults/compaction/compact.ts +59 -0
- package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
- package/src/plugins/defaults/compaction/manager-store.ts +57 -0
- package/src/plugins/defaults/compaction/package.json +1 -2
- package/src/plugins/defaults/empty-response/package.json +0 -1
- package/src/plugins/defaults/history-repair/package.json +0 -1
- package/src/plugins/defaults/index.ts +135 -26
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
- package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
- 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__/live-integration.test.ts +9 -6
- 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 +8 -5
- 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 +10 -5
- 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 +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
- 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/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
- 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 +5 -5
- 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 +7 -7
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
- package/src/plugins/defaults/title-generate/package.json +0 -1
- package/src/plugins/defaults/tool-error/package.json +0 -1
- package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
- package/src/plugins/pipeline.ts +6 -293
- package/src/plugins/registry.ts +9 -37
- package/src/plugins/types.ts +76 -381
- package/src/plugins/user-loader.ts +30 -127
- package/src/prompts/__tests__/system-prompt.test.ts +6 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
- package/src/runtime/__tests__/agent-wake.test.ts +555 -691
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +108 -209
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +315 -55
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
- package/src/runtime/routes/acp-routes.test.ts +89 -25
- package/src/runtime/routes/acp-routes.ts +81 -29
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +6 -10
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +28 -2
- package/src/runtime/routes/conversation-starter-routes.ts +13 -7
- package/src/runtime/routes/conversations-import-routes.ts +24 -7
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-cu-routes.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +18 -3
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
- package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
- package/src/runtime/routes/memory-v3-routes.ts +16 -6
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +337 -35
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/signals/cancel.ts +2 -4
- package/src/subagent/manager.ts +21 -5
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +8 -2
- package/src/tools/acp/spawn.test.ts +176 -195
- package/src/tools/acp/spawn.ts +37 -172
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +166 -50
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/ui-surface/definitions.ts +25 -5
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
- package/docs/plugins.md +0 -832
- package/examples/plugins/echo/register.ts +0 -143
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
- package/src/__tests__/compaction-pipeline.test.ts +0 -210
- package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
- package/src/__tests__/pipeline-runner.test.ts +0 -554
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/daemon/wake-target-adapter.ts +0 -253
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
- package/src/plugins/defaults/circuit-breaker/package.json +0 -15
- package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
- package/src/plugins/defaults/compaction/register.ts +0 -35
- package/src/plugins/defaults/compaction/terminal.ts +0 -73
- package/src/plugins/defaults/empty-response/register.ts +0 -23
- package/src/plugins/defaults/history-repair/register.ts +0 -24
- package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
- package/src/plugins/defaults/overflow-reduce/package.json +0 -15
- package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
- package/src/plugins/defaults/title-generate/register.ts +0 -35
- package/src/plugins/defaults/tool-error/register.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
- package/src/plugins/external-api.ts +0 -104
- package/src/proactive-artifact/aux-message-injector.ts +0 -97
- package/src/proactive-artifact/decision.test.ts +0 -226
- package/src/proactive-artifact/decision.ts +0 -165
- package/src/proactive-artifact/index.ts +0 -7
- package/src/proactive-artifact/job.test.ts +0 -962
- package/src/proactive-artifact/job.ts +0 -372
- package/src/proactive-artifact/message-copy.ts +0 -58
- package/src/proactive-artifact/trigger-state.test.ts +0 -286
- package/src/proactive-artifact/trigger-state.ts +0 -123
- /package/src/{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__/health.test.ts +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__/provider-blocks.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__/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}/health.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +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
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for {@link fetchMarketplaceEntries} and {@link resolveMarketplaceSource}.
|
|
3
|
+
*
|
|
4
|
+
* Network is replaced with an in-memory fixture passed via the `fetch`
|
|
5
|
+
* dependency — no globals are monkey-patched.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
import type { FetchLike } from "../install-from-github.js";
|
|
11
|
+
import {
|
|
12
|
+
fetchMarketplaceEntries,
|
|
13
|
+
type MarketplaceEntry,
|
|
14
|
+
MarketplaceFetchError,
|
|
15
|
+
resolveMarketplaceSource,
|
|
16
|
+
} from "../plugin-marketplace.js";
|
|
17
|
+
|
|
18
|
+
const MANIFEST_URL_PREFIX =
|
|
19
|
+
"https://api.github.com/repos/vellum-ai/vellum-assistant/contents/experimental/plugins/marketplace.json";
|
|
20
|
+
|
|
21
|
+
/** Serve `body` (any value) as the raw manifest file at the manifest URL. */
|
|
22
|
+
function manifestFetch(body: unknown, status = 200, raw?: string): FetchLike {
|
|
23
|
+
return (async (input: RequestInfo | URL) => {
|
|
24
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
25
|
+
if (!url.startsWith(MANIFEST_URL_PREFIX)) {
|
|
26
|
+
return new Response("unexpected url: " + url, { status: 500 });
|
|
27
|
+
}
|
|
28
|
+
if (status !== 200) {
|
|
29
|
+
return new Response("error", { status });
|
|
30
|
+
}
|
|
31
|
+
return new Response(raw ?? JSON.stringify(body), { status: 200 });
|
|
32
|
+
}) as FetchLike;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const VALID_MANIFEST = {
|
|
36
|
+
name: "vellum-assistant",
|
|
37
|
+
owner: { name: "Vellum" },
|
|
38
|
+
plugins: [
|
|
39
|
+
{
|
|
40
|
+
name: "caveman",
|
|
41
|
+
source: {
|
|
42
|
+
source: "github",
|
|
43
|
+
repo: "JuliusBrussee/caveman",
|
|
44
|
+
ref: "v1.8.2",
|
|
45
|
+
},
|
|
46
|
+
description: "Ultra-compressed communication mode.",
|
|
47
|
+
category: "productivity",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "nested",
|
|
51
|
+
source: {
|
|
52
|
+
source: "github",
|
|
53
|
+
repo: "acme/monorepo",
|
|
54
|
+
path: "packages/nested",
|
|
55
|
+
ref: "abc123",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
describe("fetchMarketplaceEntries", () => {
|
|
62
|
+
test("parses a valid manifest and returns its plugin entries", async () => {
|
|
63
|
+
// GIVEN a canonical repo serving a valid marketplace manifest
|
|
64
|
+
const fetch = manifestFetch(VALID_MANIFEST);
|
|
65
|
+
|
|
66
|
+
// WHEN we fetch the marketplace entries at a ref
|
|
67
|
+
const entries = await fetchMarketplaceEntries({ fetch }, { ref: "main" });
|
|
68
|
+
|
|
69
|
+
// THEN both whitelisted plugins are returned with their pinned sources
|
|
70
|
+
expect(entries.map((e) => e.name)).toEqual(["caveman", "nested"]);
|
|
71
|
+
expect(entries[0]!.source).toEqual({
|
|
72
|
+
source: "github",
|
|
73
|
+
repo: "JuliusBrussee/caveman",
|
|
74
|
+
ref: "v1.8.2",
|
|
75
|
+
});
|
|
76
|
+
expect(entries[1]!.source.path).toBe("packages/nested");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("forwards the ref to the GitHub Contents API", async () => {
|
|
80
|
+
// GIVEN a fixture that records the ref query parameter
|
|
81
|
+
let seenRef: string | undefined;
|
|
82
|
+
const fetch: FetchLike = (async (input: RequestInfo | URL) => {
|
|
83
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
84
|
+
seenRef = /[?&]ref=([^&]+)/.exec(url)?.[1];
|
|
85
|
+
return new Response(JSON.stringify(VALID_MANIFEST), { status: 200 });
|
|
86
|
+
}) as FetchLike;
|
|
87
|
+
|
|
88
|
+
// WHEN we fetch at an explicit ref
|
|
89
|
+
await fetchMarketplaceEntries({ fetch }, { ref: "feat-branch" });
|
|
90
|
+
|
|
91
|
+
// THEN the ref reaches GitHub
|
|
92
|
+
expect(seenRef).toBe("feat-branch");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("treats a missing manifest (404) as an empty whitelist", async () => {
|
|
96
|
+
// GIVEN no manifest exists at this ref
|
|
97
|
+
const fetch = manifestFetch(null, 404);
|
|
98
|
+
|
|
99
|
+
// WHEN we fetch the entries
|
|
100
|
+
const entries = await fetchMarketplaceEntries({ fetch }, { ref: "main" });
|
|
101
|
+
|
|
102
|
+
// THEN the absence is a normal empty result, not an error
|
|
103
|
+
expect(entries).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("throws MarketplaceFetchError on non-404 HTTP failure", async () => {
|
|
107
|
+
// GIVEN GitHub returns a server error
|
|
108
|
+
const fetch = manifestFetch(null, 503);
|
|
109
|
+
|
|
110
|
+
// WHEN / THEN the failure surfaces as a typed error
|
|
111
|
+
await expect(
|
|
112
|
+
fetchMarketplaceEntries({ fetch }, { ref: "main" }),
|
|
113
|
+
).rejects.toBeInstanceOf(MarketplaceFetchError);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("throws MarketplaceFetchError on non-JSON body", async () => {
|
|
117
|
+
// GIVEN the manifest body is not valid JSON
|
|
118
|
+
const fetch = manifestFetch(undefined, 200, "{ not json");
|
|
119
|
+
|
|
120
|
+
// WHEN / THEN parsing fails loudly
|
|
121
|
+
await expect(
|
|
122
|
+
fetchMarketplaceEntries({ fetch }, { ref: "main" }),
|
|
123
|
+
).rejects.toBeInstanceOf(MarketplaceFetchError);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("rejects a manifest entry missing a pinned ref", async () => {
|
|
127
|
+
// GIVEN an entry whose source omits the required ref
|
|
128
|
+
const fetch = manifestFetch({
|
|
129
|
+
name: "x",
|
|
130
|
+
plugins: [
|
|
131
|
+
{ name: "floating", source: { source: "github", repo: "a/b" } },
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// WHEN / THEN schema validation rejects the unpinned source
|
|
136
|
+
await expect(
|
|
137
|
+
fetchMarketplaceEntries({ fetch }, { ref: "main" }),
|
|
138
|
+
).rejects.toBeInstanceOf(MarketplaceFetchError);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("rejects a path that escapes the repo root", async () => {
|
|
142
|
+
// GIVEN an entry whose path contains a parent-segment escape
|
|
143
|
+
const fetch = manifestFetch({
|
|
144
|
+
name: "x",
|
|
145
|
+
plugins: [
|
|
146
|
+
{
|
|
147
|
+
name: "escape",
|
|
148
|
+
source: {
|
|
149
|
+
source: "github",
|
|
150
|
+
repo: "a/b",
|
|
151
|
+
path: "../../etc",
|
|
152
|
+
ref: "main",
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// WHEN / THEN the unsafe path is rejected
|
|
159
|
+
await expect(
|
|
160
|
+
fetchMarketplaceEntries({ fetch }, { ref: "main" }),
|
|
161
|
+
).rejects.toBeInstanceOf(MarketplaceFetchError);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("resolveMarketplaceSource", () => {
|
|
166
|
+
const entries: readonly MarketplaceEntry[] = [
|
|
167
|
+
{
|
|
168
|
+
name: "caveman",
|
|
169
|
+
source: {
|
|
170
|
+
source: "github",
|
|
171
|
+
repo: "JuliusBrussee/caveman",
|
|
172
|
+
ref: "v1.8.2",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "nested",
|
|
177
|
+
source: {
|
|
178
|
+
source: "github",
|
|
179
|
+
repo: "acme/monorepo",
|
|
180
|
+
path: "packages/nested",
|
|
181
|
+
ref: "abc123",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
test("resolves a repo-root plugin to owner/repo with empty path", () => {
|
|
187
|
+
// GIVEN whitelisted entries
|
|
188
|
+
// WHEN resolving a name whose source has no sub-path
|
|
189
|
+
const resolved = resolveMarketplaceSource("caveman", entries);
|
|
190
|
+
|
|
191
|
+
// THEN the owner/repo split out and path defaults to repo root
|
|
192
|
+
expect(resolved).toEqual({
|
|
193
|
+
owner: "JuliusBrussee",
|
|
194
|
+
repo: "caveman",
|
|
195
|
+
path: "",
|
|
196
|
+
ref: "v1.8.2",
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("resolves a sub-path plugin to its directory", () => {
|
|
201
|
+
// GIVEN whitelisted entries
|
|
202
|
+
// WHEN resolving a name whose source pins a sub-path
|
|
203
|
+
const resolved = resolveMarketplaceSource("nested", entries);
|
|
204
|
+
|
|
205
|
+
// THEN the sub-path is preserved
|
|
206
|
+
expect(resolved).toEqual({
|
|
207
|
+
owner: "acme",
|
|
208
|
+
repo: "monorepo",
|
|
209
|
+
path: "packages/nested",
|
|
210
|
+
ref: "abc123",
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("returns null for a name not in the whitelist", () => {
|
|
215
|
+
// GIVEN whitelisted entries
|
|
216
|
+
// WHEN resolving an unknown name
|
|
217
|
+
// THEN no source is returned (caller falls back to first-party)
|
|
218
|
+
expect(resolveMarketplaceSource("unknown", entries)).toBeNull();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -11,6 +11,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
11
11
|
import {
|
|
12
12
|
type FetchLike,
|
|
13
13
|
InvalidSearchPatternError,
|
|
14
|
+
PluginCatalogUnavailableError,
|
|
14
15
|
searchPlugins,
|
|
15
16
|
} from "../search-plugins.js";
|
|
16
17
|
|
|
@@ -180,44 +181,73 @@ describe("searchPlugins", () => {
|
|
|
180
181
|
expect(result.matches.map((m) => m.name)).toEqual(["simple-memory"]);
|
|
181
182
|
});
|
|
182
183
|
|
|
183
|
-
test("HTTP 5xx
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
184
|
+
test("HTTP 5xx is a transient PluginCatalogUnavailableError", async () => {
|
|
185
|
+
// GIVEN GitHub returns a 5xx (upstream outage)
|
|
186
|
+
// WHEN we search
|
|
187
|
+
// THEN the failure is classified transient so the caller can serve a
|
|
188
|
+
// stale cache and the route can map it to 503 (not a misleading 500).
|
|
189
|
+
const err = await searchPlugins(
|
|
190
|
+
{ query: "memory" },
|
|
191
|
+
{
|
|
192
|
+
fetch: (async () =>
|
|
193
|
+
new Response("upstream broken", { status: 503 })) as FetchLike,
|
|
194
|
+
},
|
|
195
|
+
).catch((e: unknown) => e);
|
|
196
|
+
expect(err).toBeInstanceOf(PluginCatalogUnavailableError);
|
|
197
|
+
expect((err as PluginCatalogUnavailableError).status).toBe(503);
|
|
193
198
|
});
|
|
194
199
|
|
|
195
|
-
test("
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
200
|
+
test("rate-limited 403 (x-ratelimit-remaining: 0) is transient", async () => {
|
|
201
|
+
// GIVEN GitHub returns 403 with the rate-limit budget exhausted
|
|
202
|
+
// WHEN we search
|
|
203
|
+
// THEN it's transient — this is exactly the unauthenticated 60 req/hr
|
|
204
|
+
// exhaustion that empties the catalog, and it must surface as 503.
|
|
205
|
+
const err = await searchPlugins(
|
|
206
|
+
{ query: "memory" },
|
|
207
|
+
{
|
|
208
|
+
fetch: (async () =>
|
|
209
|
+
new Response("rate limit exceeded", {
|
|
210
|
+
status: 403,
|
|
211
|
+
headers: { "x-ratelimit-remaining": "0" },
|
|
212
|
+
})) as FetchLike,
|
|
213
|
+
},
|
|
214
|
+
).catch((e: unknown) => e);
|
|
215
|
+
expect(err).toBeInstanceOf(PluginCatalogUnavailableError);
|
|
216
|
+
expect((err as PluginCatalogUnavailableError).status).toBe(403);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("a 403 without the rate-limit signal is a hard error", async () => {
|
|
220
|
+
// GIVEN a bare 403 (genuine permissions problem, not rate limiting)
|
|
221
|
+
// WHEN we search
|
|
222
|
+
// THEN it is NOT transient — serving a stale catalog would mask a real
|
|
223
|
+
// misconfiguration, so it propagates as a plain error.
|
|
224
|
+
const err = await searchPlugins(
|
|
225
|
+
{ query: "memory" },
|
|
226
|
+
{
|
|
227
|
+
fetch: (async () =>
|
|
228
|
+
new Response("forbidden", { status: 403 })) as FetchLike,
|
|
229
|
+
},
|
|
230
|
+
).catch((e: unknown) => e);
|
|
231
|
+
expect(err).toBeInstanceOf(Error);
|
|
232
|
+
expect(err).not.toBeInstanceOf(PluginCatalogUnavailableError);
|
|
233
|
+
expect((err as Error).message).toMatch(/HTTP 403/);
|
|
205
234
|
});
|
|
206
235
|
|
|
207
|
-
test("404 on the plugins prefix
|
|
236
|
+
test("404 on the plugins prefix is a hard error (not silently empty)", async () => {
|
|
208
237
|
// Distinct from `installPlugin`, where 404 on a specific plugin name is
|
|
209
238
|
// normal "not found". For the search, 404 on the prefix means the
|
|
210
|
-
// canonical source path itself is gone — that's
|
|
211
|
-
// worth surfacing, not a clean empty result.
|
|
212
|
-
await
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
).
|
|
239
|
+
// canonical source path itself is gone — that's a real misconfiguration
|
|
240
|
+
// worth surfacing, not a clean empty result and not a transient outage.
|
|
241
|
+
const err = await searchPlugins(
|
|
242
|
+
{ query: "memory" },
|
|
243
|
+
{
|
|
244
|
+
fetch: (async () =>
|
|
245
|
+
new Response("not found", { status: 404 })) as FetchLike,
|
|
246
|
+
},
|
|
247
|
+
).catch((e: unknown) => e);
|
|
248
|
+
expect(err).toBeInstanceOf(Error);
|
|
249
|
+
expect(err).not.toBeInstanceOf(PluginCatalogUnavailableError);
|
|
250
|
+
expect((err as Error).message).toMatch(/HTTP 404/);
|
|
221
251
|
});
|
|
222
252
|
|
|
223
253
|
test("returns matches sorted by name", async () => {
|
|
@@ -238,6 +268,170 @@ describe("searchPlugins", () => {
|
|
|
238
268
|
]);
|
|
239
269
|
});
|
|
240
270
|
|
|
271
|
+
test("merges whitelisted marketplace entries with first-party dirs", async () => {
|
|
272
|
+
// GIVEN the canonical repo serves both a first-party plugin listing and a
|
|
273
|
+
// marketplace manifest whitelisting an external plugin
|
|
274
|
+
const manifest = {
|
|
275
|
+
name: "vellum-assistant",
|
|
276
|
+
plugins: [
|
|
277
|
+
{
|
|
278
|
+
name: "caveman",
|
|
279
|
+
source: {
|
|
280
|
+
source: "github",
|
|
281
|
+
repo: "JuliusBrussee/caveman",
|
|
282
|
+
ref: "v1.8.2",
|
|
283
|
+
},
|
|
284
|
+
description: "Ultra-compressed communication mode.",
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
};
|
|
288
|
+
const fetch: FetchLike = (async (input: RequestInfo | URL) => {
|
|
289
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
290
|
+
if (url.includes("marketplace.json")) {
|
|
291
|
+
return new Response(JSON.stringify(manifest), { status: 200 });
|
|
292
|
+
}
|
|
293
|
+
return new Response(
|
|
294
|
+
JSON.stringify([
|
|
295
|
+
{
|
|
296
|
+
name: "simple-memory",
|
|
297
|
+
path: "experimental/plugins/simple-memory",
|
|
298
|
+
type: "dir",
|
|
299
|
+
size: 0,
|
|
300
|
+
download_url: null,
|
|
301
|
+
},
|
|
302
|
+
]),
|
|
303
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
304
|
+
);
|
|
305
|
+
}) as FetchLike;
|
|
306
|
+
|
|
307
|
+
// WHEN we search the catalog with a match-all query
|
|
308
|
+
const result = await searchPlugins({ query: "" }, { fetch });
|
|
309
|
+
|
|
310
|
+
// THEN both sources appear, sorted by name, each tagged with its origin
|
|
311
|
+
expect(result.matches).toEqual([
|
|
312
|
+
{
|
|
313
|
+
name: "caveman",
|
|
314
|
+
path: "github:JuliusBrussee/caveman@v1.8.2",
|
|
315
|
+
description: "Ultra-compressed communication mode.",
|
|
316
|
+
source: {
|
|
317
|
+
kind: "github",
|
|
318
|
+
repo: "JuliusBrussee/caveman",
|
|
319
|
+
ref: "v1.8.2",
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: "simple-memory",
|
|
324
|
+
path: "experimental/plugins/simple-memory",
|
|
325
|
+
source: { kind: "first-party" },
|
|
326
|
+
},
|
|
327
|
+
]);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("filters marketplace entries by the query too", async () => {
|
|
331
|
+
// GIVEN a marketplace whitelisting an external plugin and no first-party dirs
|
|
332
|
+
const manifest = {
|
|
333
|
+
name: "vellum-assistant",
|
|
334
|
+
plugins: [
|
|
335
|
+
{
|
|
336
|
+
name: "caveman",
|
|
337
|
+
source: {
|
|
338
|
+
source: "github",
|
|
339
|
+
repo: "JuliusBrussee/caveman",
|
|
340
|
+
ref: "v1.8.2",
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
};
|
|
345
|
+
const fetch: FetchLike = (async (input: RequestInfo | URL) => {
|
|
346
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
347
|
+
if (url.includes("marketplace.json")) {
|
|
348
|
+
return new Response(JSON.stringify(manifest), { status: 200 });
|
|
349
|
+
}
|
|
350
|
+
return new Response(JSON.stringify([]), { status: 200 });
|
|
351
|
+
}) as FetchLike;
|
|
352
|
+
|
|
353
|
+
// WHEN the query matches no plugin name
|
|
354
|
+
const result = await searchPlugins({ query: "memory" }, { fetch });
|
|
355
|
+
|
|
356
|
+
// THEN the non-matching marketplace entry is excluded
|
|
357
|
+
expect(result.matches).toEqual([]);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("first-party dirs win a name collision with the marketplace", async () => {
|
|
361
|
+
// GIVEN both a first-party dir and a marketplace entry named "caveman"
|
|
362
|
+
const manifest = {
|
|
363
|
+
name: "vellum-assistant",
|
|
364
|
+
plugins: [
|
|
365
|
+
{
|
|
366
|
+
name: "caveman",
|
|
367
|
+
source: {
|
|
368
|
+
source: "github",
|
|
369
|
+
repo: "JuliusBrussee/caveman",
|
|
370
|
+
ref: "v1.8.2",
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
};
|
|
375
|
+
const fetch: FetchLike = (async (input: RequestInfo | URL) => {
|
|
376
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
377
|
+
if (url.includes("marketplace.json")) {
|
|
378
|
+
return new Response(JSON.stringify(manifest), { status: 200 });
|
|
379
|
+
}
|
|
380
|
+
return new Response(
|
|
381
|
+
JSON.stringify([
|
|
382
|
+
{
|
|
383
|
+
name: "caveman",
|
|
384
|
+
path: "experimental/plugins/caveman",
|
|
385
|
+
type: "dir",
|
|
386
|
+
size: 0,
|
|
387
|
+
download_url: null,
|
|
388
|
+
},
|
|
389
|
+
]),
|
|
390
|
+
{ status: 200 },
|
|
391
|
+
);
|
|
392
|
+
}) as FetchLike;
|
|
393
|
+
|
|
394
|
+
// WHEN we search
|
|
395
|
+
const result = await searchPlugins({ query: "caveman" }, { fetch });
|
|
396
|
+
|
|
397
|
+
// THEN only the first-party entry surfaces — the manifest is additive
|
|
398
|
+
expect(result.matches).toEqual([
|
|
399
|
+
{
|
|
400
|
+
name: "caveman",
|
|
401
|
+
path: "experimental/plugins/caveman",
|
|
402
|
+
source: { kind: "first-party" },
|
|
403
|
+
},
|
|
404
|
+
]);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test("a broken marketplace manifest degrades to the first-party listing", async () => {
|
|
408
|
+
// GIVEN the manifest is malformed but the first-party listing is healthy
|
|
409
|
+
const fetch: FetchLike = (async (input: RequestInfo | URL) => {
|
|
410
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
411
|
+
if (url.includes("marketplace.json")) {
|
|
412
|
+
return new Response("{ not json", { status: 200 });
|
|
413
|
+
}
|
|
414
|
+
return new Response(
|
|
415
|
+
JSON.stringify([
|
|
416
|
+
{
|
|
417
|
+
name: "simple-memory",
|
|
418
|
+
path: "experimental/plugins/simple-memory",
|
|
419
|
+
type: "dir",
|
|
420
|
+
size: 0,
|
|
421
|
+
download_url: null,
|
|
422
|
+
},
|
|
423
|
+
]),
|
|
424
|
+
{ status: 200 },
|
|
425
|
+
);
|
|
426
|
+
}) as FetchLike;
|
|
427
|
+
|
|
428
|
+
// WHEN we search
|
|
429
|
+
const result = await searchPlugins({ query: "" }, { fetch });
|
|
430
|
+
|
|
431
|
+
// THEN the core catalog is unaffected by the broken whitelist
|
|
432
|
+
expect(result.matches.map((m) => m.name)).toEqual(["simple-memory"]);
|
|
433
|
+
});
|
|
434
|
+
|
|
241
435
|
test("sends no Authorization header (canonical source is a public repo)", async () => {
|
|
242
436
|
let seenAuth: string | undefined;
|
|
243
437
|
let seenUserAgent: string | undefined;
|