@vellumai/assistant 0.8.10 → 0.8.11-staging.1
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/bun.lock +62 -1
- package/docs/workspace-tools.md +196 -0
- package/examples/plugins/echo/README.md +3 -3
- package/knip.json +1 -0
- package/openapi.yaml +460 -128
- package/package.json +2 -1
- package/scripts/build-plugin-api.ts +299 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
- package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
- package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
- package/src/__tests__/agent-loop-thinking.test.ts +4 -0
- package/src/__tests__/agent-loop.test.ts +578 -5
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -1
- package/src/__tests__/build-persisted-content.test.ts +75 -1
- package/src/__tests__/catalog-install-normalize.test.ts +141 -0
- package/src/__tests__/ces-startup-timeout.test.ts +60 -0
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
- package/src/__tests__/context-overflow-reducer.test.ts +264 -124
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
- package/src/__tests__/conversation-agent-loop.test.ts +131 -551
- package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-fork-crud.test.ts +259 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-lifecycle.test.ts +257 -1
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
- package/src/__tests__/conversation-queue.test.ts +1 -39
- package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
- package/src/__tests__/conversation-skill-tools.test.ts +491 -5
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-store.test.ts +74 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
- package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
- package/src/__tests__/credential-execution-tools.test.ts +23 -11
- package/src/__tests__/credential-security-invariants.test.ts +6 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
- package/src/__tests__/device-id.test.ts +70 -1
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
- package/src/__tests__/empty-response-hook.test.ts +242 -66
- package/src/__tests__/external-plugin-loader.test.ts +0 -31
- package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
- package/src/__tests__/history-repair-hook.test.ts +228 -3
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-browser-proxy.test.ts +254 -9
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/image-recovery-hook.test.ts +387 -0
- package/src/__tests__/injector-chain.test.ts +5 -4
- package/src/__tests__/injector-v3-suppression.test.ts +373 -47
- package/src/__tests__/intent-routing.test.ts +7 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/oauth-store.test.ts +0 -85
- package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
- package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
- package/src/__tests__/persist-unsendable-image.test.ts +4 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
- package/src/__tests__/plugin-bootstrap.test.ts +82 -73
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -8
- package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
- package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
- package/src/__tests__/registry.test.ts +240 -1
- package/src/__tests__/require-fresh-approval.test.ts +3 -0
- package/src/__tests__/schedule-routes.test.ts +116 -1
- package/src/__tests__/schedule-store.test.ts +28 -0
- package/src/__tests__/schedule-tools.test.ts +94 -1
- package/src/__tests__/server-history-render.test.ts +39 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
- package/src/__tests__/skills.test.ts +202 -0
- package/src/__tests__/slim-skill-category.test.ts +195 -0
- package/src/__tests__/strip-memory-injections.test.ts +33 -39
- package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
- package/src/__tests__/title-generate-hook.test.ts +9 -7
- package/src/__tests__/tool-audit-listener.test.ts +264 -1
- package/src/__tests__/tool-error-hook.test.ts +4 -3
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/tool-start-timestamp.test.ts +218 -0
- package/src/__tests__/tools-get-route.test.ts +202 -0
- package/src/__tests__/workspace-tool-loader.test.ts +319 -0
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +569 -319
- package/src/api/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/index.ts +10 -0
- package/src/api/responses/conversation-message.ts +135 -27
- package/src/api/responses/memory-v3-selection-log.ts +4 -4
- package/src/approvals/guardian-request-resolvers.ts +26 -0
- package/src/browser-session/backends/host-bridge.ts +29 -0
- package/src/browser-session/index.ts +1 -0
- package/src/browser-session/types.ts +5 -1
- package/src/cli/commands/__tests__/schedules.test.ts +62 -4
- package/src/cli/commands/__tests__/skills.test.ts +53 -0
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/inference-providers.ts +0 -8
- package/src/cli/commands/plugins.ts +2 -2
- package/src/cli/commands/schedules.ts +27 -4
- package/src/cli/commands/skills.ts +187 -146
- package/src/cli/commands/tools.ts +106 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
- package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
- package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
- package/src/cli/lib/install-from-github.ts +187 -117
- package/src/cli/lib/plugin-catalog-cache.ts +9 -9
- package/src/cli/lib/plugin-details.ts +38 -68
- package/src/cli/lib/plugin-marketplace.ts +42 -14
- package/src/cli/lib/search-plugins.ts +29 -129
- package/src/cli/program.ts +2 -0
- package/src/config/bundled-skills/acp/SKILL.md +1 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
- package/src/config/bundled-skills/app-control/SKILL.md +1 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
- package/src/config/bundled-skills/contacts/SKILL.md +1 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
- package/src/config/bundled-skills/followups/SKILL.md +1 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
- package/src/config/bundled-skills/messaging/SKILL.md +1 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
- package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
- package/src/config/bundled-skills/sequences/SKILL.md +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +1 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
- package/src/config/bundled-skills/subagent/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
- package/src/config/env-registry.ts +23 -0
- package/src/config/feature-flag-registry.json +17 -81
- package/src/config/loader.ts +5 -22
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
- package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
- package/src/config/schemas/compaction-logs.ts +79 -0
- package/src/config/schemas/memory-v2.ts +0 -8
- package/src/config/schemas/memory-v3.ts +104 -33
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/config/skills.ts +117 -47
- package/src/context/compactor.ts +11 -0
- package/src/context/strip-injections.ts +38 -4
- package/src/credential-execution/feature-gates.ts +0 -21
- package/src/credential-execution/startup-timeout.ts +32 -4
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
- package/src/daemon/conversation-agent-loop.ts +123 -658
- package/src/daemon/conversation-error.ts +6 -33
- package/src/daemon/conversation-lifecycle.ts +1 -1
- package/src/daemon/conversation-runtime-assembly.ts +183 -22
- package/src/daemon/conversation-skill-tools.ts +137 -8
- package/src/daemon/conversation-slash.ts +0 -14
- package/src/daemon/conversation-store.ts +2 -19
- package/src/daemon/conversation-tool-setup.ts +87 -1
- package/src/daemon/conversation.ts +119 -50
- package/src/daemon/external-plugins-bootstrap.ts +36 -95
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/shared.ts +9 -1
- package/src/daemon/handlers/skills.ts +10 -4
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-browser-proxy.ts +117 -22
- package/src/daemon/lifecycle.ts +34 -5
- package/src/daemon/message-protocol.ts +0 -7
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/skills.ts +17 -0
- package/src/daemon/providers-setup.ts +3 -0
- package/src/daemon/server.ts +3 -3
- package/src/daemon/tool-setup-types.ts +9 -3
- package/src/daemon/trust-context.ts +23 -0
- package/src/daemon/workspace-tools-watcher.ts +324 -0
- package/src/events/tool-audit-listener.ts +78 -16
- package/src/events/tool-metrics-listener.ts +2 -5
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
- package/src/memory/__tests__/conversation-queries.test.ts +176 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
- package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
- package/src/memory/conversation-crud.ts +134 -3
- package/src/memory/conversation-queries.ts +64 -7
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.test.ts +130 -1
- package/src/memory/embedding-backend.ts +79 -106
- package/src/memory/embedding-gemini.ts +5 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
- package/src/memory/graph/conversation-graph-memory.ts +36 -25
- package/src/memory/graph/tool-handlers.ts +3 -0
- package/src/memory/job-handlers/cleanup.ts +3 -1
- package/src/memory/jobs-store.ts +0 -28
- package/src/memory/jobs-worker.ts +10 -22
- package/src/memory/memory-marker.ts +29 -0
- package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
- package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
- package/src/memory/migrations/270-schedule-description.ts +36 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
- package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
- package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
- package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
- package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
- package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +16 -0
- package/src/memory/schema/infrastructure.ts +26 -0
- package/src/memory/skill-loaded-events-store.test.ts +160 -0
- package/src/memory/skill-loaded-events-store.ts +95 -0
- package/src/memory/tool-executed-events-store.test.ts +219 -0
- package/src/memory/tool-executed-events-store.ts +102 -0
- package/src/memory/tool-usage-store.ts +15 -3
- package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
- package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
- package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
- package/src/memory/v2/__tests__/page-store.test.ts +33 -0
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
- package/src/memory/v2/activation-store.ts +50 -1
- package/src/memory/v2/consolidation-job.ts +113 -29
- package/src/memory/v2/injected-block-slugs.ts +79 -0
- package/src/memory/v2/injection.ts +6 -1
- package/src/memory/v2/prompts/consolidation.ts +414 -13
- package/src/memory/v2/router.ts +2 -28
- package/src/memory/v2/static-context.ts +1 -1
- package/src/memory/v2/types.ts +16 -0
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +2 -1
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/oauth/oauth-store.ts +0 -9
- package/src/permissions/checker.test.ts +83 -1
- package/src/permissions/checker.ts +25 -2
- package/src/permissions/gateway-threshold-reader.test.ts +182 -0
- package/src/permissions/gateway-threshold-reader.ts +80 -0
- package/src/platform/client.ts +1 -3
- package/src/platform/feature-gate.ts +3 -12
- package/src/plugin-api/constants.ts +4 -2
- package/src/plugin-api/index.ts +63 -11
- package/src/plugin-api/types.ts +236 -71
- package/src/plugins/defaults/compaction/compact.ts +66 -2
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +303 -1
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
- package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
- package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
- package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
- package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
- package/src/plugins/defaults/history-repair/terminal.ts +39 -2
- package/src/plugins/defaults/image-recovery/detect.ts +25 -0
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
- package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
- package/src/plugins/defaults/image-recovery/package.json +14 -0
- package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
- package/src/plugins/defaults/index.ts +71 -5
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
- package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
- package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
- package/src/plugins/defaults/memory-retrieval/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
- package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
- package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
- package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
- package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
- package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
- package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
- package/src/plugins/pipeline.ts +8 -5
- package/src/plugins/types.ts +5 -51
- package/src/providers/cache-control.ts +26 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
- package/src/providers/model-catalog.ts +13 -1
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
- package/src/providers/openai/chat-completions-provider.ts +46 -0
- package/src/providers/openai/responses-provider.ts +45 -0
- package/src/providers/registry.ts +0 -8
- package/src/runtime/__tests__/agent-wake.test.ts +0 -1
- package/src/runtime/agent-wake.ts +13 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
- package/src/runtime/routes/acp-routes.test.ts +106 -0
- package/src/runtime/routes/acp-routes.ts +248 -2
- package/src/runtime/routes/browser-tabs-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +14 -5
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
- package/src/runtime/routes/consolidation-routes.ts +6 -82
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +71 -0
- package/src/runtime/routes/identity-routes.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
- package/src/runtime/routes/plugins-routes.ts +34 -45
- package/src/runtime/routes/schedule-routes.ts +43 -5
- package/src/runtime/routes/settings-routes.ts +140 -15
- package/src/runtime/routes/skills-routes.ts +18 -6
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
- package/src/runtime/services/conversation-serializer.ts +38 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/schedule/schedule-store.ts +19 -0
- package/src/skills/catalog-install.ts +77 -13
- package/src/tasks/task-scheduler.ts +1 -0
- package/src/telemetry/types.ts +66 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
- package/src/telemetry/usage-telemetry-reporter.ts +213 -20
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
- package/src/tools/browser/browser-execution.ts +27 -9
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
- package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
- package/src/tools/browser/cdp-client/factory.ts +217 -17
- package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
- package/src/tools/browser/cdp-client/types.ts +22 -2
- package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
- package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
- package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
- package/src/tools/executor.ts +39 -7
- package/src/tools/registry.ts +387 -5
- package/src/tools/schedule/create.ts +16 -0
- package/src/tools/schedule/list.ts +12 -4
- package/src/tools/schedule/update.ts +12 -0
- package/src/tools/skills/load.ts +11 -6
- package/src/tools/terminal/safe-env.ts +2 -0
- package/src/tools/types.ts +65 -9
- package/src/tools/workspace-tools/loader.ts +673 -0
- package/src/usage/attribution.ts +28 -0
- package/src/util/device-id.ts +17 -3
- package/src/util/platform.ts +16 -0
- package/tsconfig.plugin-api.json +13 -0
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
- package/src/daemon/message-types/browser.ts +0 -10
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
- package/src/plugins/external-api.ts +0 -114
- package/src/plugins/plugin-skill-contributions.ts +0 -292
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Install a plugin by name from the canonical GitHub source.
|
|
3
3
|
*
|
|
4
|
-
* A name resolves to
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 2. Otherwise the first-party convention
|
|
18
|
-
* `vellum-ai/vellum-assistant/experimental/plugins/<name>/` at the
|
|
19
|
-
* configured ref, fetched via the GitHub Contents API (a small handful of
|
|
20
|
-
* in-repo files — cloning the whole monorepo to install one would be
|
|
21
|
-
* wasteful).
|
|
4
|
+
* A name resolves to a whitelisted external ecosystem plugin — an entry in the
|
|
5
|
+
* curated `plugins/marketplace.json` manifest. The pinned
|
|
6
|
+
* `owner/repo[/path]@ref` (see {@link ./plugin-marketplace}) is fetched with a
|
|
7
|
+
* shallow `git` clone at that ref — one network operation regardless of repo
|
|
8
|
+
* size, immune to GitHub's unauthenticated API rate-limit, and recording the
|
|
9
|
+
* exact resolved commit for provenance — and materialized into
|
|
10
|
+
* `<workspacePluginsDir>/<name>/` so the daemon discovers it on next start.
|
|
11
|
+
*
|
|
12
|
+
* When we curate an adapter stub for the plugin (a `plugins/<name>/` directory
|
|
13
|
+
* in this repo with a `scripts.postinstall` command), the stub is overlaid
|
|
14
|
+
* onto the clone and its postinstall runs to translate a foreign-ecosystem
|
|
15
|
+
* layout into the shape Vellum's loader runs (see {@link applyAdapterStub}). A
|
|
16
|
+
* name with no marketplace entry is a not-found.
|
|
22
17
|
*
|
|
23
18
|
* Designed for direct programmatic use. The CLI command
|
|
24
19
|
* `assistant plugins install <name>` is a thin wrapper that supplies
|
|
@@ -47,6 +42,8 @@ import { ensureBun } from "../../util/bun-runtime.js";
|
|
|
47
42
|
import { getWorkspacePluginsDir } from "../../util/platform.js";
|
|
48
43
|
import {
|
|
49
44
|
fetchMarketplaceEntries,
|
|
45
|
+
MarketplaceFetchError,
|
|
46
|
+
type ResolvedPluginSource,
|
|
50
47
|
resolveMarketplaceSource,
|
|
51
48
|
} from "./plugin-marketplace.js";
|
|
52
49
|
|
|
@@ -54,7 +51,7 @@ const execFileAsync = promisify(execFile);
|
|
|
54
51
|
|
|
55
52
|
const PLUGIN_SOURCE_OWNER = "vellum-ai";
|
|
56
53
|
const PLUGIN_SOURCE_REPO = "vellum-assistant";
|
|
57
|
-
const PLUGIN_SOURCE_PATH_PREFIX = "
|
|
54
|
+
const PLUGIN_SOURCE_PATH_PREFIX = "plugins";
|
|
58
55
|
/** Default git ref to fetch from when callers don't override. */
|
|
59
56
|
export const DEFAULT_PLUGIN_REF = "main";
|
|
60
57
|
|
|
@@ -130,7 +127,7 @@ export interface InstallPluginResult {
|
|
|
130
127
|
readonly target: string;
|
|
131
128
|
readonly fileCount: number;
|
|
132
129
|
readonly ref: string;
|
|
133
|
-
/** Resolved commit SHA
|
|
130
|
+
/** Resolved commit SHA the external source was cloned at; null when it could not be read. */
|
|
134
131
|
readonly commit: string | null;
|
|
135
132
|
}
|
|
136
133
|
|
|
@@ -214,8 +211,6 @@ function isTransientUpstreamStatus(res: Response): boolean {
|
|
|
214
211
|
|
|
215
212
|
/** Resolved GitHub coordinates a plugin name is fetched from. */
|
|
216
213
|
interface PluginFetchSource {
|
|
217
|
-
/** Whether the plugin lives in our monorepo or an external whitelisted repo. */
|
|
218
|
-
readonly kind: "first-party" | "external";
|
|
219
214
|
readonly owner: string;
|
|
220
215
|
readonly repo: string;
|
|
221
216
|
/** Repo-relative directory holding the plugin root; `""` = repo root. */
|
|
@@ -230,54 +225,42 @@ function sourceLabel(source: PluginFetchSource): string {
|
|
|
230
225
|
: `${source.owner}/${source.repo}`;
|
|
231
226
|
}
|
|
232
227
|
|
|
233
|
-
/** First-party `experimental/plugins/<name>` coordinates at a given ref. */
|
|
234
|
-
function firstPartySource(name: string, ref: string): PluginFetchSource {
|
|
235
|
-
return {
|
|
236
|
-
kind: "first-party",
|
|
237
|
-
owner: PLUGIN_SOURCE_OWNER,
|
|
238
|
-
repo: PLUGIN_SOURCE_REPO,
|
|
239
|
-
rootPath: `${PLUGIN_SOURCE_PATH_PREFIX}/${name}`,
|
|
240
|
-
ref,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
228
|
/**
|
|
245
|
-
* Resolve a plugin name to concrete GitHub coordinates
|
|
229
|
+
* Resolve a plugin name to the concrete GitHub coordinates of its pinned
|
|
230
|
+
* marketplace entry, or `null` when no entry claims the name.
|
|
246
231
|
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* stub* (a curated `package.json` + postinstall script overlaid onto the clone
|
|
252
|
-
* to translate it into Vellum's shape; see {@link applyAdapterStub}), not a
|
|
253
|
-
* standalone first-party plugin. So letting the marketplace win the name is
|
|
254
|
-
* what makes the stub apply to the external clone, and the search catalog
|
|
255
|
-
* surfaces the same name as external — install and search stay in agreement.
|
|
232
|
+
* The marketplace is external-only by construction — a same-named
|
|
233
|
+
* `plugins/<name>` directory is the plugin's optional *adapter stub* (a curated
|
|
234
|
+
* `package.json` + postinstall script overlaid onto the clone to translate it
|
|
235
|
+
* into Vellum's shape; see {@link applyAdapterStub}), not a standalone plugin.
|
|
256
236
|
*
|
|
257
|
-
* A
|
|
258
|
-
*
|
|
259
|
-
*
|
|
237
|
+
* A transient marketplace failure (rate-limit / 5xx) surfaces as a retryable
|
|
238
|
+
* {@link PluginSourceUnavailableError}; a malformed manifest propagates as a
|
|
239
|
+
* hard error, since the marketplace is the source of truth for what is
|
|
240
|
+
* installable.
|
|
260
241
|
*/
|
|
261
242
|
async function resolvePluginSource(
|
|
262
243
|
name: string,
|
|
263
244
|
marketplaceRef: string,
|
|
264
245
|
fetchFn: FetchLike,
|
|
265
|
-
): Promise<PluginFetchSource> {
|
|
266
|
-
let resolved
|
|
246
|
+
): Promise<PluginFetchSource | null> {
|
|
247
|
+
let resolved: ResolvedPluginSource | null;
|
|
267
248
|
try {
|
|
268
249
|
const entries = await fetchMarketplaceEntries(
|
|
269
250
|
{ fetch: fetchFn },
|
|
270
251
|
{ ref: marketplaceRef },
|
|
271
252
|
);
|
|
272
253
|
resolved = resolveMarketplaceSource(name, entries);
|
|
273
|
-
} catch {
|
|
274
|
-
|
|
254
|
+
} catch (err) {
|
|
255
|
+
if (err instanceof MarketplaceFetchError && err.transient) {
|
|
256
|
+
throw new PluginSourceUnavailableError(err.message, err.status ?? 503);
|
|
257
|
+
}
|
|
258
|
+
throw err;
|
|
275
259
|
}
|
|
276
260
|
|
|
277
|
-
if (!resolved) return
|
|
261
|
+
if (!resolved) return null;
|
|
278
262
|
|
|
279
263
|
return {
|
|
280
|
-
kind: "external",
|
|
281
264
|
owner: resolved.owner,
|
|
282
265
|
repo: resolved.repo,
|
|
283
266
|
rootPath: resolved.path,
|
|
@@ -288,7 +271,7 @@ async function resolvePluginSource(
|
|
|
288
271
|
/**
|
|
289
272
|
* Reject plugin names that could escape the canonical source path or the
|
|
290
273
|
* install target. The source convention is a flat namespace under
|
|
291
|
-
* `
|
|
274
|
+
* `plugins/`, so a legitimate name is a single path segment
|
|
292
275
|
* built from kebab-case alphanumerics.
|
|
293
276
|
*
|
|
294
277
|
* Exported so callers (e.g. the CLI input prompt) can validate up front
|
|
@@ -344,6 +327,13 @@ export async function installPlugin(
|
|
|
344
327
|
const force = opts.force ?? false;
|
|
345
328
|
|
|
346
329
|
const source = await resolvePluginSource(name, marketplaceRef, deps.fetch);
|
|
330
|
+
if (!source) {
|
|
331
|
+
throw new PluginNotFoundError(
|
|
332
|
+
name,
|
|
333
|
+
marketplaceRef,
|
|
334
|
+
"plugins/marketplace.json",
|
|
335
|
+
);
|
|
336
|
+
}
|
|
347
337
|
const ref = source.ref;
|
|
348
338
|
|
|
349
339
|
const pluginsDir = deps.workspacePluginsDir ?? getWorkspacePluginsDir();
|
|
@@ -353,10 +343,19 @@ export async function installPlugin(
|
|
|
353
343
|
throw new PluginAlreadyInstalledError(name, target);
|
|
354
344
|
}
|
|
355
345
|
|
|
356
|
-
// Stage
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
|
|
346
|
+
// Stage *outside* the served `plugins/` directory. The daemon watches that
|
|
347
|
+
// directory and its startup loader enumerates it, so a staging dir living
|
|
348
|
+
// inside it is observed mid-install — before the adapter overlay runs, an
|
|
349
|
+
// external clone still carries its upstream `package.json` (wrong name, no
|
|
350
|
+
// plugin-api peer dep), which the loader rejects with spurious name-mismatch
|
|
351
|
+
// and missing-peer-dependency warnings. Staging in a sibling directory keeps
|
|
352
|
+
// the half-built tree invisible until the final swap. The root is on the
|
|
353
|
+
// same filesystem as the target, so that swap stays an atomic rename.
|
|
354
|
+
// `process.pid` keeps concurrent installs of the same plugin from clobbering
|
|
355
|
+
// each other's staging.
|
|
356
|
+
const stagingRoot = join(dirname(pluginsDir), ".plugins-staging");
|
|
357
|
+
mkdirSync(stagingRoot, { recursive: true });
|
|
358
|
+
const stagingDir = join(stagingRoot, `${name}.installing.${process.pid}`);
|
|
360
359
|
if (existsSync(stagingDir)) {
|
|
361
360
|
rmSync(stagingDir, { recursive: true, force: true });
|
|
362
361
|
}
|
|
@@ -365,52 +364,20 @@ export async function installPlugin(
|
|
|
365
364
|
let fileCount: number;
|
|
366
365
|
let commit: string | null = null;
|
|
367
366
|
try {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
await applyAdapterStub(name, marketplaceRef, stagingDir, deps);
|
|
383
|
-
}
|
|
384
|
-
} else {
|
|
385
|
-
fileCount = await copyDir(
|
|
386
|
-
source.owner,
|
|
387
|
-
source.repo,
|
|
388
|
-
source.rootPath,
|
|
389
|
-
ref,
|
|
390
|
-
stagingDir,
|
|
391
|
-
deps.fetch,
|
|
392
|
-
);
|
|
393
|
-
// We only land in the first-party branch for this name when the
|
|
394
|
-
// marketplace lookup returned no claim. A *healthy* marketplace that
|
|
395
|
-
// claims the name routes to the external+adapter branch above; reaching
|
|
396
|
-
// here for a directory that is actually an adapter stub (declares a
|
|
397
|
-
// `scripts.postinstall`) therefore means the marketplace failed to load
|
|
398
|
-
// (rate-limit / 5xx / malformed) and we degraded past it. A stub has no
|
|
399
|
-
// hooks/tools of its own — it only transforms an external clone — so
|
|
400
|
-
// installing it alone would materialize a non-functional plugin. Fail
|
|
401
|
-
// loudly and retryably instead of silently shipping a broken plugin.
|
|
402
|
-
// Genuine first-party plugins (no postinstall) install normally.
|
|
403
|
-
if (
|
|
404
|
-
fileCount > 0 &&
|
|
405
|
-
resolvePostinstallScript(name, stagingDir) !== null
|
|
406
|
-
) {
|
|
407
|
-
throw new PluginPostinstallError(
|
|
408
|
-
name,
|
|
409
|
-
"resolved to a first-party adapter stub, but its marketplace entry " +
|
|
410
|
-
"could not be read to locate the external source it adapts — the " +
|
|
411
|
-
"marketplace lookup likely failed transiently. Retry the install.",
|
|
412
|
-
);
|
|
413
|
-
}
|
|
367
|
+
const cloned = await copyExternalViaGit(
|
|
368
|
+
source,
|
|
369
|
+
stagingDir,
|
|
370
|
+
deps.runGit ?? defaultGitRunner,
|
|
371
|
+
);
|
|
372
|
+
fileCount = cloned.fileCount;
|
|
373
|
+
commit = cloned.commit;
|
|
374
|
+
// An external clone is often a foreign-ecosystem plugin (e.g. a Claude
|
|
375
|
+
// Code plugin) that the Vellum loader can't run as-is. When we curate an
|
|
376
|
+
// adapter stub for it, overlay the stub and run its transform so the
|
|
377
|
+
// materialized tree is a valid Vellum plugin. Raw clones (no stub) are
|
|
378
|
+
// left untouched.
|
|
379
|
+
if (fileCount > 0) {
|
|
380
|
+
await applyAdapterStub(name, marketplaceRef, stagingDir, deps);
|
|
414
381
|
}
|
|
415
382
|
} catch (err) {
|
|
416
383
|
rmSync(stagingDir, { recursive: true, force: true });
|
|
@@ -431,6 +398,9 @@ export async function installPlugin(
|
|
|
431
398
|
// Atomic-ish swap: rmSync + renameSync. On POSIX the rename itself is
|
|
432
399
|
// atomic, so the only window where the target is absent is between the
|
|
433
400
|
// rm and the rename — and at that point the staging dir is fully populated.
|
|
401
|
+
// Ensure the served `plugins/` directory exists: staging now lives outside
|
|
402
|
+
// it, so the target's parent is no longer created as a side effect.
|
|
403
|
+
mkdirSync(pluginsDir, { recursive: true });
|
|
434
404
|
if (existsSync(target)) {
|
|
435
405
|
rmSync(target, { recursive: true, force: true });
|
|
436
406
|
}
|
|
@@ -534,14 +504,16 @@ const POSTINSTALL_TIMEOUT_MS = 60_000;
|
|
|
534
504
|
* Overlay our curated adapter stub onto a freshly cloned external plugin and
|
|
535
505
|
* run its postinstall transform, returning whether a transform ran.
|
|
536
506
|
*
|
|
537
|
-
* The stub lives at `
|
|
507
|
+
* The stub lives at `plugins/<name>/` in our own repo and carries
|
|
538
508
|
* a `package.json` (with a `scripts.postinstall` adapter command) plus the
|
|
539
509
|
* adapter script it names. We fetch it via the Contents API — a couple of
|
|
540
|
-
* small files, well within the rate limit — and copy it over the clone
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
* the
|
|
510
|
+
* small files, well within the rate limit — and copy it over the clone so the
|
|
511
|
+
* postinstall we run is ours, never the upstream repo's lifecycle script. The
|
|
512
|
+
* overlaid stub `package.json` exists only to name that adapter; the installed
|
|
513
|
+
* plugin's manifest is rebuilt from the upstream `package.json` afterwards (see
|
|
514
|
+
* {@link normalizeInstalledManifest}). Absent a stub (the common case for a
|
|
515
|
+
* plugin already in Vellum shape), nothing is overlaid and the clone is
|
|
516
|
+
* installed as-is.
|
|
545
517
|
*
|
|
546
518
|
* On any adapter failure the error propagates so {@link installPlugin} rolls
|
|
547
519
|
* back staging — better to fail loudly than ship a half-transformed plugin.
|
|
@@ -552,6 +524,11 @@ async function applyAdapterStub(
|
|
|
552
524
|
stagingDir: string,
|
|
553
525
|
deps: InstallPluginDeps,
|
|
554
526
|
): Promise<boolean> {
|
|
527
|
+
// Capture the cloned upstream manifest before the stub overlay replaces it,
|
|
528
|
+
// so the installed plugin can preserve it verbatim except for the two fields
|
|
529
|
+
// the Vellum loader requires (name + plugin-api peer dep).
|
|
530
|
+
const upstreamPkg = readPackageJson(join(stagingDir, "package.json"));
|
|
531
|
+
|
|
555
532
|
const stubFileCount = await copyDir(
|
|
556
533
|
PLUGIN_SOURCE_OWNER,
|
|
557
534
|
PLUGIN_SOURCE_REPO,
|
|
@@ -571,9 +548,89 @@ async function applyAdapterStub(
|
|
|
571
548
|
} catch (err) {
|
|
572
549
|
throw new PluginPostinstallError(name, subprocessErrorText(err));
|
|
573
550
|
}
|
|
551
|
+
|
|
552
|
+
normalizeInstalledManifest(name, stagingDir, upstreamPkg);
|
|
574
553
|
return true;
|
|
575
554
|
}
|
|
576
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Default `@vellumai/plugin-api` peer-dependency range stamped onto an adapted
|
|
558
|
+
* plugin that doesn't already declare one.
|
|
559
|
+
*/
|
|
560
|
+
const PLUGIN_API_PEER_RANGE = ">=0.8.0";
|
|
561
|
+
|
|
562
|
+
type PackageManifest = Record<string, unknown>;
|
|
563
|
+
|
|
564
|
+
/** Parse the `package.json` at `path`, or null if it's absent or unparseable. */
|
|
565
|
+
function readPackageJson(path: string): PackageManifest | null {
|
|
566
|
+
if (!existsSync(path)) return null;
|
|
567
|
+
try {
|
|
568
|
+
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
569
|
+
return typeof parsed === "object" &&
|
|
570
|
+
parsed !== null &&
|
|
571
|
+
!Array.isArray(parsed)
|
|
572
|
+
? (parsed as PackageManifest)
|
|
573
|
+
: null;
|
|
574
|
+
} catch {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Write the installed plugin's final `package.json` after the adapter has run.
|
|
581
|
+
*
|
|
582
|
+
* A curated adapter stub deliberately overlays its own `package.json` onto the
|
|
583
|
+
* clone so the installer can find and run the stub's `scripts.postinstall`.
|
|
584
|
+
* That stub is install-time machinery, not the plugin's manifest, so once the
|
|
585
|
+
* adapter has run we rebuild the manifest from the upstream `package.json`
|
|
586
|
+
* captured before the overlay — preserving its `version`, `description`,
|
|
587
|
+
* `license`, and every other field — and mutate only what the Vellum loader
|
|
588
|
+
* requires: `name` must equal the install directory, and `@vellumai/plugin-api`
|
|
589
|
+
* must be declared as a peer dependency. The spent `postinstall` script is
|
|
590
|
+
* dropped so the installed plugin carries no install-time machinery.
|
|
591
|
+
*
|
|
592
|
+
* When the upstream repo shipped no `package.json`, the overlaid stub is the
|
|
593
|
+
* only manifest available, so it becomes the base instead.
|
|
594
|
+
*/
|
|
595
|
+
function normalizeInstalledManifest(
|
|
596
|
+
name: string,
|
|
597
|
+
stagingDir: string,
|
|
598
|
+
upstreamPkg: PackageManifest | null,
|
|
599
|
+
): void {
|
|
600
|
+
const manifestPath = join(stagingDir, "package.json");
|
|
601
|
+
const base = upstreamPkg ?? readPackageJson(manifestPath) ?? {};
|
|
602
|
+
|
|
603
|
+
const peer =
|
|
604
|
+
typeof base.peerDependencies === "object" && base.peerDependencies !== null
|
|
605
|
+
? (base.peerDependencies as Record<string, unknown>)
|
|
606
|
+
: {};
|
|
607
|
+
const existingRange = peer["@vellumai/plugin-api"];
|
|
608
|
+
|
|
609
|
+
const manifest: PackageManifest = {
|
|
610
|
+
...base,
|
|
611
|
+
name,
|
|
612
|
+
peerDependencies: {
|
|
613
|
+
...peer,
|
|
614
|
+
"@vellumai/plugin-api":
|
|
615
|
+
typeof existingRange === "string"
|
|
616
|
+
? existingRange
|
|
617
|
+
: PLUGIN_API_PEER_RANGE,
|
|
618
|
+
},
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
if (typeof manifest.scripts === "object" && manifest.scripts !== null) {
|
|
622
|
+
const scripts = { ...(manifest.scripts as Record<string, unknown>) };
|
|
623
|
+
delete scripts.postinstall;
|
|
624
|
+
if (Object.keys(scripts).length === 0) {
|
|
625
|
+
delete manifest.scripts;
|
|
626
|
+
} else {
|
|
627
|
+
manifest.scripts = scripts;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
632
|
+
}
|
|
633
|
+
|
|
577
634
|
/**
|
|
578
635
|
* Resolve the absolute path of the adapter script named by the (overlaid stub)
|
|
579
636
|
* `package.json`'s `scripts.postinstall`, or `null` when there is no stub
|
|
@@ -695,7 +752,8 @@ function pluginPostinstallEnv(bun: string): NodeJS.ProcessEnv {
|
|
|
695
752
|
|
|
696
753
|
/**
|
|
697
754
|
* Recursively copy regular files from `srcRoot` into `destDir`, skipping the
|
|
698
|
-
* top-level `.git` directory
|
|
755
|
+
* top-level `.git` directory, a top-level `bunfig.toml` (see below), and any
|
|
756
|
+
* symlinks. Returns the file count.
|
|
699
757
|
*/
|
|
700
758
|
function copyTreeSkippingGit(srcRoot: string, destDir: string): number {
|
|
701
759
|
let count = 0;
|
|
@@ -705,6 +763,17 @@ function copyTreeSkippingGit(srcRoot: string, destDir: string): number {
|
|
|
705
763
|
// Drop git metadata and symlinks: the loader follows neither, and a
|
|
706
764
|
// symlink could otherwise point outside the staging tree.
|
|
707
765
|
if (relDir === "" && entry.name === ".git") continue;
|
|
766
|
+
// Drop a top-level `bunfig.toml`. The adapter postinstall runs `bun` with
|
|
767
|
+
// its cwd at the staged root, and Bun auto-loads `$cwd/bunfig.toml` as
|
|
768
|
+
// project config — including a `preload` list it executes before the
|
|
769
|
+
// entry point. An upstream config would therefore run arbitrary code
|
|
770
|
+
// ahead of the curated adapter, defeating the command/env guards. Bun
|
|
771
|
+
// reads only the cwd's file (it neither walks up nor descends), so
|
|
772
|
+
// dropping it at the root closes the vector; a Vellum plugin never
|
|
773
|
+
// consumes `bunfig.toml`. Match case-insensitively because the macOS
|
|
774
|
+
// install target's filesystem is case-insensitive, where Bun would still
|
|
775
|
+
// open a clone-supplied `BUNFIG.TOML`.
|
|
776
|
+
if (relDir === "" && entry.name.toLowerCase() === "bunfig.toml") continue;
|
|
708
777
|
if (entry.isSymbolicLink()) continue;
|
|
709
778
|
|
|
710
779
|
const rel = relDir ? join(relDir, entry.name) : entry.name;
|
|
@@ -799,7 +868,7 @@ function writeInstallManifest(
|
|
|
799
868
|
const manifest = {
|
|
800
869
|
name,
|
|
801
870
|
source: {
|
|
802
|
-
kind:
|
|
871
|
+
kind: "github",
|
|
803
872
|
owner: source.owner,
|
|
804
873
|
repo: source.repo,
|
|
805
874
|
path: source.rootPath || undefined,
|
|
@@ -815,13 +884,14 @@ function writeInstallManifest(
|
|
|
815
884
|
}
|
|
816
885
|
|
|
817
886
|
/**
|
|
818
|
-
* Recursively copy a
|
|
887
|
+
* Recursively copy a curated adapter stub directory via the GitHub Contents API.
|
|
819
888
|
*
|
|
820
|
-
*
|
|
821
|
-
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
889
|
+
* An adapter stub lives in our own monorepo at `plugins/<name>/` as a small
|
|
890
|
+
* handful of files (a `package.json` + postinstall script), so the
|
|
891
|
+
* per-directory walk stays well within the unauthenticated rate limit — and
|
|
892
|
+
* avoids cloning the entire repository just to overlay it. Returns the number
|
|
893
|
+
* of files written; zero means no stub exists at this ref, in which case the
|
|
894
|
+
* external clone is installed as-is.
|
|
825
895
|
*/
|
|
826
896
|
async function copyDir(
|
|
827
897
|
owner: string,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* In-memory cache for the installable plugin catalog.
|
|
3
3
|
*
|
|
4
|
-
* The catalog (`loadPluginCatalog`) is built from
|
|
5
|
-
* Contents API
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* The catalog (`loadPluginCatalog`) is built from a single unauthenticated
|
|
5
|
+
* GitHub Contents API call — the `marketplace.json` manifest. GitHub's
|
|
6
|
+
* unauthenticated rate limit is 60 requests/hour per IP, so a long-lived
|
|
7
|
+
* daemon that hit GitHub on every catalog search (on web mount, on every
|
|
8
|
+
* keystroke, across reloads and multiple clients) would exhaust that budget
|
|
9
|
+
* and start getting HTTP 403s.
|
|
10
10
|
*
|
|
11
11
|
* Because the GitHub responses are **query-independent** — `searchPlugins`
|
|
12
12
|
* applies the regex filter in memory — one catalog load serves any number of
|
|
@@ -44,9 +44,9 @@ const cache = new Map<string, CacheEntry>();
|
|
|
44
44
|
* transient upstream error ({@link PluginCatalogUnavailableError} — rate
|
|
45
45
|
* limiting or a 5xx) and a cached catalog exists, the stale catalog is served
|
|
46
46
|
* (and its TTL window is reset so a sustained outage doesn't re-enter the
|
|
47
|
-
* failing fetch on every call). A hard error (e.g.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* failing fetch on every call). A hard error (e.g. a malformed manifest, or a
|
|
48
|
+
* deleted ref) always propagates — serving stale there would silently mask a
|
|
49
|
+
* real misconfiguration. With no cache to fall
|
|
50
50
|
* back on, the underlying error propagates regardless so the caller can
|
|
51
51
|
* surface it (e.g. the route maps a rate-limit failure to 503).
|
|
52
52
|
*/
|
|
@@ -6,18 +6,19 @@
|
|
|
6
6
|
* most authoritative for each field:
|
|
7
7
|
* 1. The locally installed copy under `<workspacePluginsDir>/<name>/`, when
|
|
8
8
|
* present — its `package.json` and `README.md` are read straight off disk.
|
|
9
|
-
* 2. The curated `
|
|
9
|
+
* 2. The curated `plugins/marketplace.json` entry, for external
|
|
10
10
|
* ecosystem plugins (description / homepage / license / pinned source).
|
|
11
|
-
* 3. The plugin's own repository at the pinned
|
|
12
|
-
* `experimental/plugins/<name>/` or the external `owner/repo[/path]`),
|
|
11
|
+
* 3. The plugin's own external repository at the pinned `owner/repo[/path]`,
|
|
13
12
|
* fetched via the GitHub Contents API for the README and any
|
|
14
13
|
* `package.json` fields the manifest doesn't carry.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* The `source` field is the marketplace entry's pinned origin when one claims
|
|
16
|
+
* the name, otherwise `null` — an installed copy with no catalog entry has no
|
|
17
|
+
* advertised origin. Name-collision precedence matches {@link ./search-plugins}
|
|
18
|
+
* and {@link ./install-from-github}: a marketplace entry owns its name, so the
|
|
18
19
|
* detail page advertises the external source the catalog and installer use. A
|
|
19
|
-
* same-named `
|
|
20
|
-
*
|
|
20
|
+
* same-named `plugins/<name>/` directory is that plugin's adapter stub, not a
|
|
21
|
+
* standalone plugin, so it does not override the claim.
|
|
21
22
|
*
|
|
22
23
|
* Designed for direct programmatic use with an injected `fetch`, mirroring the
|
|
23
24
|
* sibling plugin libraries.
|
|
@@ -38,10 +39,6 @@ import {
|
|
|
38
39
|
} from "./plugin-marketplace.js";
|
|
39
40
|
import type { PluginMatchSource } from "./search-plugins.js";
|
|
40
41
|
|
|
41
|
-
const PLUGIN_SOURCE_OWNER = "vellum-ai";
|
|
42
|
-
const PLUGIN_SOURCE_REPO = "vellum-assistant";
|
|
43
|
-
const PLUGIN_SOURCE_PATH_PREFIX = "experimental/plugins";
|
|
44
|
-
|
|
45
42
|
/** Recognised README filenames, matched case-insensitively against a listing. */
|
|
46
43
|
const README_RE = /^readme(\.md|\.markdown)?$/i;
|
|
47
44
|
|
|
@@ -91,8 +88,11 @@ export interface PluginDetails {
|
|
|
91
88
|
readonly license: string | null;
|
|
92
89
|
/** Resolved version (installed copy first, then repo `package.json`); `null` when unknown. */
|
|
93
90
|
readonly version: string | null;
|
|
94
|
-
/**
|
|
95
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Pinned origin, mirroring the catalog's {@link PluginMatchSource}; `null`
|
|
93
|
+
* when an installed copy has no marketplace entry to advertise an origin.
|
|
94
|
+
*/
|
|
95
|
+
readonly source: PluginMatchSource | null;
|
|
96
96
|
/** README markdown, or `null` when the plugin ships none. */
|
|
97
97
|
readonly readme: string | null;
|
|
98
98
|
/** Git ref the catalog metadata / README were resolved at. */
|
|
@@ -114,8 +114,8 @@ export class PluginDetailsNotFoundError extends Error {
|
|
|
114
114
|
* Resolve the detail view for {@link opts.name}.
|
|
115
115
|
*
|
|
116
116
|
* Throws {@link PluginDetailsNotFoundError} when the name is neither installed
|
|
117
|
-
* locally nor present in the catalog
|
|
118
|
-
*
|
|
117
|
+
* locally nor present in the marketplace catalog. Network failures while
|
|
118
|
+
* enriching from GitHub degrade to the fields
|
|
119
119
|
* already known from disk / the manifest rather than failing the whole view —
|
|
120
120
|
* a detail page that renders metadata without a README beats a hard error.
|
|
121
121
|
*/
|
|
@@ -132,40 +132,24 @@ export async function getPluginDetails(
|
|
|
132
132
|
|
|
133
133
|
const marketplaceEntry = await findMarketplaceEntry(name, ref, fetchFn);
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
// any, is its adapter stub) — so only probe the first-party directory when
|
|
137
|
-
// the name is unclaimed, which also spares a GitHub request in the common
|
|
138
|
-
// external case.
|
|
139
|
-
const firstPartyEntries =
|
|
140
|
-
marketplaceEntry === null
|
|
141
|
-
? await listDirSafe(
|
|
142
|
-
PLUGIN_SOURCE_OWNER,
|
|
143
|
-
PLUGIN_SOURCE_REPO,
|
|
144
|
-
`${PLUGIN_SOURCE_PATH_PREFIX}/${name}`,
|
|
145
|
-
ref,
|
|
146
|
-
fetchFn,
|
|
147
|
-
)
|
|
148
|
-
: null;
|
|
149
|
-
const firstPartyExists = firstPartyEntries !== null;
|
|
150
|
-
|
|
151
|
-
if (!local.installed && !firstPartyExists && !marketplaceEntry) {
|
|
135
|
+
if (!local.installed && !marketplaceEntry) {
|
|
152
136
|
throw new PluginDetailsNotFoundError(name, ref);
|
|
153
137
|
}
|
|
154
138
|
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
const source: PluginMatchSource = useExternal
|
|
139
|
+
const source: PluginMatchSource | null = marketplaceEntry
|
|
158
140
|
? {
|
|
159
141
|
kind: "github",
|
|
160
|
-
repo: marketplaceEntry
|
|
161
|
-
ref: marketplaceEntry
|
|
162
|
-
...(marketplaceEntry
|
|
163
|
-
? { path: marketplaceEntry
|
|
142
|
+
repo: marketplaceEntry.source.repo,
|
|
143
|
+
ref: marketplaceEntry.source.ref,
|
|
144
|
+
...(marketplaceEntry.source.path
|
|
145
|
+
? { path: marketplaceEntry.source.path }
|
|
164
146
|
: {}),
|
|
165
147
|
}
|
|
166
|
-
:
|
|
148
|
+
: null;
|
|
167
149
|
|
|
168
|
-
const remote =
|
|
150
|
+
const remote = source
|
|
151
|
+
? await readRemotePlugin(source, fetchFn)
|
|
152
|
+
: { manifest: emptyManifest(), readme: null };
|
|
169
153
|
|
|
170
154
|
const readme = local.readme ?? remote.readme;
|
|
171
155
|
|
|
@@ -244,39 +228,25 @@ interface RemotePlugin {
|
|
|
244
228
|
}
|
|
245
229
|
|
|
246
230
|
/**
|
|
247
|
-
* Fetch README + `package.json` from the plugin's repository.
|
|
231
|
+
* Fetch README + `package.json` from the plugin's external repository.
|
|
248
232
|
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
233
|
+
* Lists the plugin's `owner/repo[/path]` directory fresh at its own pinned
|
|
234
|
+
* `source.ref` — the detail page must describe the same artifact the installer
|
|
235
|
+
* resolves from `source.ref`, even when that differs from the catalog ref
|
|
236
|
+
* (`main`).
|
|
253
237
|
*/
|
|
254
238
|
async function readRemotePlugin(
|
|
255
239
|
source: PluginMatchSource,
|
|
256
|
-
firstPartyEntries: readonly GitHubContentEntry[] | null,
|
|
257
240
|
fetchFn: FetchLike,
|
|
258
241
|
): Promise<RemotePlugin> {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// from `source.ref`, even when that differs from the catalog ref (`main`).
|
|
268
|
-
entries = await listDirSafe(
|
|
269
|
-
owner,
|
|
270
|
-
repo,
|
|
271
|
-
source.path ?? "",
|
|
272
|
-
source.ref,
|
|
273
|
-
fetchFn,
|
|
274
|
-
);
|
|
275
|
-
} else {
|
|
276
|
-
owner = PLUGIN_SOURCE_OWNER;
|
|
277
|
-
repo = PLUGIN_SOURCE_REPO;
|
|
278
|
-
entries = firstPartyEntries;
|
|
279
|
-
}
|
|
242
|
+
const [owner, repo] = source.repo.split("/", 2) as [string, string];
|
|
243
|
+
const entries = await listDirSafe(
|
|
244
|
+
owner,
|
|
245
|
+
repo,
|
|
246
|
+
source.path ?? "",
|
|
247
|
+
source.ref,
|
|
248
|
+
fetchFn,
|
|
249
|
+
);
|
|
280
250
|
|
|
281
251
|
if (!entries) {
|
|
282
252
|
return { manifest: emptyManifest(), readme: null };
|