@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae
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/docs/activation-funnel-telemetry.md +310 -0
- 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 +263 -121
- package/package.json +2 -2
- package/src/__tests__/activation-early-marking.test.ts +120 -0
- 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 +23 -8
- 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 +6 -5
- 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 +146 -0
- package/src/__tests__/compaction-events.test.ts +7 -7
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
- package/src/__tests__/context-overflow-reducer.test.ts +265 -125
- package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
- package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
- 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 +158 -234
- package/src/__tests__/conversation-agent-loop.test.ts +342 -623
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
- package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-history-web-search.test.ts +6 -1
- package/src/__tests__/conversation-media-retry.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
- package/src/__tests__/conversation-process-callsite.test.ts +15 -15
- package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
- package/src/__tests__/conversation-queue.test.ts +10 -10
- package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
- package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
- package/src/__tests__/conversation-slash-queue.test.ts +7 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
- package/src/__tests__/conversation-speed-override.test.ts +11 -10
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
- package/src/__tests__/conversation-undo.test.ts +2 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
- package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +36 -0
- 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 +17 -2
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -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 +5 -1
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/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__/llm-resolver.test.ts +73 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
- package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
- package/src/__tests__/persist-unsendable-image.test.ts +97 -1
- package/src/__tests__/plugin-api-shim.test.ts +6 -9
- package/src/__tests__/plugin-bootstrap.test.ts +91 -81
- package/src/__tests__/plugin-registry.test.ts +3 -49
- package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -70
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
- package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
- package/src/__tests__/published-app-updater.test.ts +138 -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 +31 -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 +5 -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/__tests__/web-fetch.test.ts +45 -0
- package/src/acp/__tests__/agent-process.test.ts +161 -0
- package/src/acp/__tests__/client-handler.test.ts +40 -0
- package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
- 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/prepare-agent-env.ts +80 -27
- package/src/acp/resolve-agent.test.ts +188 -28
- package/src/acp/resolve-agent.ts +119 -42
- 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 +403 -251
- package/src/api/responses/conversation-message.ts +22 -2
- package/src/api/responses/memory-v3-selection-log.ts +19 -10
- package/src/approvals/guardian-request-resolvers.ts +27 -1
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
- package/src/cli/commands/memory-v3.ts +57 -199
- package/src/cli/commands/plugins.ts +43 -37
- package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
- package/src/cli/lib/install-from-github.ts +797 -60
- package/src/cli/lib/plugin-catalog-cache.ts +84 -0
- package/src/cli/lib/plugin-details.ts +416 -0
- package/src/cli/lib/plugin-marketplace.ts +215 -0
- package/src/cli/lib/search-plugins.ts +201 -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 +9 -3
- package/src/config/acp-schema.ts +1 -7
- package/src/config/bundled-skills/acp/SKILL.md +80 -41
- 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 -3
- package/src/config/feature-flag-registry.json +10 -16
- package/src/config/llm-resolver.ts +39 -7
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
- package/src/config/schemas/call-site-catalog.ts +0 -21
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +17 -3
- package/src/config/schemas/memory-v3.ts +58 -8
- package/src/config/seed-inference-profiles.ts +18 -0
- package/src/context/compactor.ts +22 -4
- package/src/context/post-turn-tool-result-truncation.ts +39 -1
- package/src/context/strip-injections.ts +8 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
- package/src/daemon/conversation-agent-loop.ts +308 -1297
- package/src/daemon/conversation-error.ts +31 -4
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +11 -255
- package/src/daemon/conversation-media-retry.ts +19 -6
- package/src/daemon/conversation-messaging.ts +17 -0
- package/src/daemon/conversation-process.ts +22 -141
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +361 -393
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +165 -11
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +444 -62
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/external-plugins-bootstrap.ts +36 -46
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-proxy-preactivation.ts +1 -3
- package/src/daemon/lifecycle.ts +21 -7
- package/src/daemon/persist-unsendable-image.ts +62 -25
- package/src/daemon/process-message.ts +1 -1
- package/src/daemon/server.ts +2 -0
- package/src/daemon/tool-side-effects.ts +15 -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__/activation-session-store.test.ts +41 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
- package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
- package/src/memory/activation-session-store.ts +43 -0
- package/src/memory/conversation-crud.ts +29 -19
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/db-init.ts +6 -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/273-onboarding-events-funnel-columns.ts +46 -0
- package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/onboarding-events-store.ts +66 -18
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
- package/src/memory/v2/__tests__/page-store.test.ts +22 -0
- package/src/memory/v2/consolidation-job.ts +2 -63
- package/src/memory/v2/types.ts +5 -0
- package/src/messaging/providers/telegram-bot/api.ts +14 -5
- 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 +9 -2
- 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/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +40 -11
- package/src/plugin-api/types.ts +108 -0
- package/src/plugins/defaults/compaction/compact.ts +106 -0
- package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/plugins/defaults/compaction/manager-store.ts +57 -0
- package/src/plugins/defaults/compaction/package.json +1 -2
- package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
- 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 +156 -26
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
- 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/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
- package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
- package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
- package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
- package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
- package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
- package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
- package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -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/external-api.ts +12 -2
- 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/system-prompt.ts +61 -10
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
- package/src/providers/anthropic/client.ts +9 -10
- package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
- package/src/providers/inference/logit-bias.ts +55 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -1
- package/src/providers/retry.ts +22 -0
- package/src/providers/types.ts +6 -0
- 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 +466 -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__/stt-routes.test.ts +112 -0
- 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 +185 -32
- package/src/runtime/routes/acp-routes.ts +328 -30
- package/src/runtime/routes/app-management-routes.ts +3 -0
- 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/channel-verification-routes.ts +14 -5
- 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 +30 -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/inbound-stages/acl-enforcement.ts +33 -34
- 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 +65 -305
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +337 -35
- package/src/runtime/routes/stt-routes.ts +45 -12
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/routes/workspace-routes.ts +50 -15
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/services/published-app-updater.ts +30 -8
- package/src/signals/cancel.ts +2 -4
- package/src/subagent/manager.ts +21 -5
- package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
- package/src/telemetry/activation-funnel.ts +167 -0
- package/src/telemetry/types.ts +13 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
- package/src/telemetry/usage-telemetry-reporter.ts +26 -1
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +9 -19
- package/src/tools/acp/list-agents.ts +3 -15
- package/src/tools/acp/spawn.test.ts +173 -202
- 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/browser/browser-execution.ts +12 -2
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/network/web-fetch.ts +65 -24
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/ui-surface/definitions.ts +32 -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/daemon/wake-target-adapter.ts +0 -253
- package/src/memory/v3/__tests__/assign.test.ts +0 -242
- package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
- package/src/memory/v3/__tests__/core.test.ts +0 -39
- package/src/memory/v3/__tests__/health.test.ts +0 -219
- package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
- package/src/memory/v3/__tests__/needle.test.ts +0 -107
- package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
- package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
- package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
- package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
- package/src/memory/v3/__tests__/router.test.ts +0 -332
- package/src/memory/v3/__tests__/selector.test.ts +0 -470
- package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
- package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
- package/src/memory/v3/__tests__/tree.test.ts +0 -192
- package/src/memory/v3/assign.ts +0 -268
- package/src/memory/v3/capabilities.ts +0 -124
- package/src/memory/v3/core.ts +0 -26
- package/src/memory/v3/health.ts +0 -0
- package/src/memory/v3/maintain-job.ts +0 -314
- package/src/memory/v3/needle.ts +0 -115
- package/src/memory/v3/orchestrate.ts +0 -126
- package/src/memory/v3/page-content.ts +0 -34
- package/src/memory/v3/provider-blocks.ts +0 -26
- package/src/memory/v3/reconcile.ts +0 -523
- package/src/memory/v3/router.ts +0 -190
- package/src/memory/v3/selection-log-store.ts +0 -84
- package/src/memory/v3/selector.ts +0 -226
- package/src/memory/v3/shadow-plugin.ts +0 -411
- package/src/memory/v3/snapshot.ts +0 -209
- package/src/memory/v3/tree.ts +0 -174
- package/src/memory/v3/types.ts +0 -59
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- 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/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
- 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/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/util/map-limit.ts +0 -27
- /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__/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}/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}/llm-retry.ts +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory cache for the installable plugin catalog.
|
|
3
|
+
*
|
|
4
|
+
* The catalog (`loadPluginCatalog`) is built from two unauthenticated GitHub
|
|
5
|
+
* Contents API calls — the first-party `experimental/plugins/` directory
|
|
6
|
+
* listing and the `marketplace.json` manifest. GitHub's unauthenticated rate
|
|
7
|
+
* limit is 60 requests/hour per IP, so a long-lived daemon that hit GitHub on
|
|
8
|
+
* every catalog search (on web mount, on every keystroke, across reloads and
|
|
9
|
+
* multiple clients) would exhaust that budget and start getting HTTP 403s.
|
|
10
|
+
*
|
|
11
|
+
* Because the GitHub responses are **query-independent** — `searchPlugins`
|
|
12
|
+
* applies the regex filter in memory — one catalog load serves any number of
|
|
13
|
+
* searches. This cache holds that load per git ref for a short TTL and, on an
|
|
14
|
+
* upstream failure, serves the last good catalog so a transient rate-limit or
|
|
15
|
+
* outage is invisible to callers. Mirrors the skills catalog cache
|
|
16
|
+
* (`src/skills/catalog-cache.ts`).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { getLogger } from "../../util/logger.js";
|
|
20
|
+
import {
|
|
21
|
+
loadPluginCatalog,
|
|
22
|
+
type PluginCatalog,
|
|
23
|
+
PluginCatalogUnavailableError,
|
|
24
|
+
type SearchPluginsDeps,
|
|
25
|
+
} from "./search-plugins.js";
|
|
26
|
+
|
|
27
|
+
const log = getLogger("plugin-catalog-cache");
|
|
28
|
+
|
|
29
|
+
/** How long a fetched catalog is served before a refresh is attempted. */
|
|
30
|
+
export const PLUGIN_CATALOG_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
31
|
+
|
|
32
|
+
interface CacheEntry {
|
|
33
|
+
catalog: PluginCatalog;
|
|
34
|
+
timestamp: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const cache = new Map<string, CacheEntry>();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the full plugin catalog at {@link ref} with in-memory caching.
|
|
41
|
+
*
|
|
42
|
+
* Within the TTL window the cached catalog is returned without touching the
|
|
43
|
+
* network. After the TTL elapses a refresh is attempted; if it fails with a
|
|
44
|
+
* transient upstream error ({@link PluginCatalogUnavailableError} — rate
|
|
45
|
+
* limiting or a 5xx) and a cached catalog exists, the stale catalog is served
|
|
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. the canonical plugins
|
|
48
|
+
* prefix returning 404, or a deleted ref) always propagates — serving stale
|
|
49
|
+
* there would silently mask a real misconfiguration. With no cache to fall
|
|
50
|
+
* back on, the underlying error propagates regardless so the caller can
|
|
51
|
+
* surface it (e.g. the route maps a rate-limit failure to 503).
|
|
52
|
+
*/
|
|
53
|
+
export async function getPluginCatalog(
|
|
54
|
+
ref: string,
|
|
55
|
+
deps: SearchPluginsDeps,
|
|
56
|
+
): Promise<PluginCatalog> {
|
|
57
|
+
const cached = cache.get(ref);
|
|
58
|
+
if (cached && Date.now() - cached.timestamp < PLUGIN_CATALOG_CACHE_TTL_MS) {
|
|
59
|
+
return cached.catalog;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const catalog = await loadPluginCatalog({ ref }, deps);
|
|
64
|
+
cache.set(ref, { catalog, timestamp: Date.now() });
|
|
65
|
+
return catalog;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (cached && err instanceof PluginCatalogUnavailableError) {
|
|
68
|
+
log.warn(
|
|
69
|
+
{ err, ref },
|
|
70
|
+
"Failed to refresh plugin catalog, serving stale cache",
|
|
71
|
+
);
|
|
72
|
+
// Reset the TTL window so subsequent calls during the outage are served
|
|
73
|
+
// from cache instead of re-entering loadPluginCatalog() every time.
|
|
74
|
+
cached.timestamp = Date.now();
|
|
75
|
+
return cached.catalog;
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Invalidate the cache (for testing or forced refresh). */
|
|
82
|
+
export function invalidatePluginCatalogCache(): void {
|
|
83
|
+
cache.clear();
|
|
84
|
+
}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a single plugin's detail view: tracked metadata plus its README.
|
|
3
|
+
*
|
|
4
|
+
* Powers the daemon's `GET /v1/plugins/:name` route and, through it, the web
|
|
5
|
+
* plugin detail page. The detail view unions three sources, preferring the
|
|
6
|
+
* most authoritative for each field:
|
|
7
|
+
* 1. The locally installed copy under `<workspacePluginsDir>/<name>/`, when
|
|
8
|
+
* present — its `package.json` and `README.md` are read straight off disk.
|
|
9
|
+
* 2. The curated `experimental/plugins/marketplace.json` entry, for external
|
|
10
|
+
* ecosystem plugins (description / homepage / license / pinned source).
|
|
11
|
+
* 3. The plugin's own repository at the pinned ref (first-party
|
|
12
|
+
* `experimental/plugins/<name>/` or the external `owner/repo[/path]`),
|
|
13
|
+
* fetched via the GitHub Contents API for the README and any
|
|
14
|
+
* `package.json` fields the manifest doesn't carry.
|
|
15
|
+
*
|
|
16
|
+
* Name-collision precedence matches {@link ./search-plugins} and
|
|
17
|
+
* {@link ./install-from-github}: a marketplace entry owns its name, so the
|
|
18
|
+
* detail page advertises the external source the catalog and installer use. A
|
|
19
|
+
* same-named `experimental/plugins/<name>/` directory is that plugin's adapter
|
|
20
|
+
* stub, not a standalone first-party plugin, so it does not override the claim.
|
|
21
|
+
*
|
|
22
|
+
* Designed for direct programmatic use with an injected `fetch`, mirroring the
|
|
23
|
+
* sibling plugin libraries.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
|
|
29
|
+
import { getWorkspacePluginsDir } from "../../util/platform.js";
|
|
30
|
+
import {
|
|
31
|
+
DEFAULT_PLUGIN_REF,
|
|
32
|
+
type FetchLike,
|
|
33
|
+
sanitizePluginName,
|
|
34
|
+
} from "./install-from-github.js";
|
|
35
|
+
import {
|
|
36
|
+
fetchMarketplaceEntries,
|
|
37
|
+
type MarketplaceEntry,
|
|
38
|
+
} from "./plugin-marketplace.js";
|
|
39
|
+
import type { PluginMatchSource } from "./search-plugins.js";
|
|
40
|
+
|
|
41
|
+
const PLUGIN_SOURCE_OWNER = "vellum-ai";
|
|
42
|
+
const PLUGIN_SOURCE_REPO = "vellum-assistant";
|
|
43
|
+
const PLUGIN_SOURCE_PATH_PREFIX = "experimental/plugins";
|
|
44
|
+
|
|
45
|
+
/** Recognised README filenames, matched case-insensitively against a listing. */
|
|
46
|
+
const README_RE = /^readme(\.md|\.markdown)?$/i;
|
|
47
|
+
|
|
48
|
+
/** Entry shape returned by the GitHub Contents API for a directory listing. */
|
|
49
|
+
interface GitHubContentEntry {
|
|
50
|
+
readonly name: string;
|
|
51
|
+
readonly path: string;
|
|
52
|
+
readonly type: "file" | "dir" | "symlink" | "submodule";
|
|
53
|
+
readonly download_url: string | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The subset of `package.json` fields the detail view surfaces. */
|
|
57
|
+
interface PluginManifestFields {
|
|
58
|
+
readonly version: string | null;
|
|
59
|
+
readonly description: string | null;
|
|
60
|
+
readonly homepage: string | null;
|
|
61
|
+
readonly license: string | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Options that control which plugin to resolve and at what ref. */
|
|
65
|
+
export interface PluginDetailsOptions {
|
|
66
|
+
/** Install name (kebab-case directory name). */
|
|
67
|
+
readonly name: string;
|
|
68
|
+
/** Git ref to read catalog metadata / README from. Defaults to {@link DEFAULT_PLUGIN_REF}. */
|
|
69
|
+
readonly ref?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Dependencies injected by the caller. */
|
|
73
|
+
export interface PluginDetailsDeps {
|
|
74
|
+
/** HTTP client. Production callers pass `globalThis.fetch.bind(globalThis)`. */
|
|
75
|
+
readonly fetch: FetchLike;
|
|
76
|
+
/** Override the workspace plugins directory. Falls back to {@link getWorkspacePluginsDir}. */
|
|
77
|
+
readonly workspacePluginsDir?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Resolved detail view for a single plugin. */
|
|
81
|
+
export interface PluginDetails {
|
|
82
|
+
/** Install name. Matches `assistant plugins install <name>`. */
|
|
83
|
+
readonly name: string;
|
|
84
|
+
/** Whether a copy is materialized under the workspace plugins directory. */
|
|
85
|
+
readonly installed: boolean;
|
|
86
|
+
/** Short description, best-effort across the three sources; `null` when unknown. */
|
|
87
|
+
readonly description: string | null;
|
|
88
|
+
/** Project homepage URL, when known; `null` otherwise. */
|
|
89
|
+
readonly homepage: string | null;
|
|
90
|
+
/** SPDX license expression, when known; `null` otherwise. */
|
|
91
|
+
readonly license: string | null;
|
|
92
|
+
/** Resolved version (installed copy first, then repo `package.json`); `null` when unknown. */
|
|
93
|
+
readonly version: string | null;
|
|
94
|
+
/** Discriminated origin, mirroring the catalog's {@link PluginMatchSource}. */
|
|
95
|
+
readonly source: PluginMatchSource;
|
|
96
|
+
/** README markdown, or `null` when the plugin ships none. */
|
|
97
|
+
readonly readme: string | null;
|
|
98
|
+
/** Git ref the catalog metadata / README were resolved at. */
|
|
99
|
+
readonly ref: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** No installed copy and no catalog/source entry claims the name. */
|
|
103
|
+
export class PluginDetailsNotFoundError extends Error {
|
|
104
|
+
constructor(
|
|
105
|
+
readonly pluginName: string,
|
|
106
|
+
readonly ref: string,
|
|
107
|
+
) {
|
|
108
|
+
super(`Plugin "${pluginName}" not found in the catalog (ref ${ref}).`);
|
|
109
|
+
this.name = "PluginDetailsNotFoundError";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Resolve the detail view for {@link opts.name}.
|
|
115
|
+
*
|
|
116
|
+
* Throws {@link PluginDetailsNotFoundError} when the name is neither installed
|
|
117
|
+
* locally nor present in the catalog (first-party directory or marketplace
|
|
118
|
+
* entry). Network failures while enriching from GitHub degrade to the fields
|
|
119
|
+
* already known from disk / the manifest rather than failing the whole view —
|
|
120
|
+
* a detail page that renders metadata without a README beats a hard error.
|
|
121
|
+
*/
|
|
122
|
+
export async function getPluginDetails(
|
|
123
|
+
opts: PluginDetailsOptions,
|
|
124
|
+
deps: PluginDetailsDeps,
|
|
125
|
+
): Promise<PluginDetails> {
|
|
126
|
+
const name = sanitizePluginName(opts.name);
|
|
127
|
+
const ref = opts.ref ?? DEFAULT_PLUGIN_REF;
|
|
128
|
+
const { fetch: fetchFn } = deps;
|
|
129
|
+
|
|
130
|
+
const pluginsDir = deps.workspacePluginsDir ?? getWorkspacePluginsDir();
|
|
131
|
+
const local = readLocalPlugin(pluginsDir, name);
|
|
132
|
+
|
|
133
|
+
const marketplaceEntry = await findMarketplaceEntry(name, ref, fetchFn);
|
|
134
|
+
|
|
135
|
+
// A marketplace entry owns its name (the same-named in-repo directory, if
|
|
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) {
|
|
152
|
+
throw new PluginDetailsNotFoundError(name, ref);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const useExternal = marketplaceEntry !== null;
|
|
156
|
+
|
|
157
|
+
const source: PluginMatchSource = useExternal
|
|
158
|
+
? {
|
|
159
|
+
kind: "github",
|
|
160
|
+
repo: marketplaceEntry!.source.repo,
|
|
161
|
+
ref: marketplaceEntry!.source.ref,
|
|
162
|
+
...(marketplaceEntry!.source.path
|
|
163
|
+
? { path: marketplaceEntry!.source.path }
|
|
164
|
+
: {}),
|
|
165
|
+
}
|
|
166
|
+
: { kind: "first-party" };
|
|
167
|
+
|
|
168
|
+
const remote = await readRemotePlugin(source, firstPartyEntries, fetchFn);
|
|
169
|
+
|
|
170
|
+
const readme = local.readme ?? remote.readme;
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
name,
|
|
174
|
+
installed: local.installed,
|
|
175
|
+
description:
|
|
176
|
+
local.manifest.description ??
|
|
177
|
+
marketplaceEntry?.description ??
|
|
178
|
+
remote.manifest.description ??
|
|
179
|
+
null,
|
|
180
|
+
homepage:
|
|
181
|
+
local.manifest.homepage ??
|
|
182
|
+
marketplaceEntry?.homepage ??
|
|
183
|
+
remote.manifest.homepage ??
|
|
184
|
+
null,
|
|
185
|
+
license:
|
|
186
|
+
local.manifest.license ??
|
|
187
|
+
marketplaceEntry?.license ??
|
|
188
|
+
remote.manifest.license ??
|
|
189
|
+
null,
|
|
190
|
+
version: local.manifest.version ?? remote.manifest.version ?? null,
|
|
191
|
+
source,
|
|
192
|
+
readme,
|
|
193
|
+
ref,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface LocalPlugin {
|
|
198
|
+
readonly installed: boolean;
|
|
199
|
+
readonly manifest: PluginManifestFields;
|
|
200
|
+
readonly readme: string | null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Read an installed copy's `package.json` + README off disk, if present. */
|
|
204
|
+
function readLocalPlugin(pluginsDir: string, name: string): LocalPlugin {
|
|
205
|
+
const target = join(pluginsDir, name);
|
|
206
|
+
if (!existsSync(target)) {
|
|
207
|
+
return { installed: false, manifest: emptyManifest(), readme: null };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const pkgPath = join(target, "package.json");
|
|
211
|
+
let manifest = emptyManifest();
|
|
212
|
+
if (existsSync(pkgPath)) {
|
|
213
|
+
try {
|
|
214
|
+
manifest = parseManifest(readFileSync(pkgPath, "utf8"));
|
|
215
|
+
} catch {
|
|
216
|
+
// A malformed local manifest degrades to empty fields — the entry is
|
|
217
|
+
// still "installed", we just have nothing extra to surface from it.
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return { installed: true, manifest, readme: readLocalReadme(target) };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Find a README file in a directory listing and read it, if any. */
|
|
225
|
+
function readLocalReadme(dir: string): string | null {
|
|
226
|
+
let names: string[];
|
|
227
|
+
try {
|
|
228
|
+
names = readdirSync(dir);
|
|
229
|
+
} catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
const readme = names.find((n) => README_RE.test(n));
|
|
233
|
+
if (!readme) return null;
|
|
234
|
+
try {
|
|
235
|
+
return readFileSync(join(dir, readme), "utf8");
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
interface RemotePlugin {
|
|
242
|
+
readonly manifest: PluginManifestFields;
|
|
243
|
+
readonly readme: string | null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Fetch README + `package.json` from the plugin's repository.
|
|
248
|
+
*
|
|
249
|
+
* For a first-party plugin the listing is the in-repo directory the caller
|
|
250
|
+
* already fetched at the catalog ref (passed through to avoid a duplicate
|
|
251
|
+
* request). For an external plugin we list its `owner/repo[/path]` directory
|
|
252
|
+
* fresh at the plugin's own pinned `source.ref`.
|
|
253
|
+
*/
|
|
254
|
+
async function readRemotePlugin(
|
|
255
|
+
source: PluginMatchSource,
|
|
256
|
+
firstPartyEntries: readonly GitHubContentEntry[] | null,
|
|
257
|
+
fetchFn: FetchLike,
|
|
258
|
+
): Promise<RemotePlugin> {
|
|
259
|
+
let owner: string;
|
|
260
|
+
let repo: string;
|
|
261
|
+
let entries: readonly GitHubContentEntry[] | null;
|
|
262
|
+
|
|
263
|
+
if (source.kind === "github") {
|
|
264
|
+
[owner, repo] = source.repo.split("/", 2) as [string, string];
|
|
265
|
+
// Read the external repo at its pinned ref, not the assistant catalog ref —
|
|
266
|
+
// the detail page must describe the same artifact the installer resolves
|
|
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
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!entries) {
|
|
282
|
+
return { manifest: emptyManifest(), readme: null };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const readmeEntry = entries.find(
|
|
286
|
+
(e) => e.type === "file" && README_RE.test(e.name),
|
|
287
|
+
);
|
|
288
|
+
const pkgEntry = entries.find(
|
|
289
|
+
(e) => e.type === "file" && e.name === "package.json",
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const [readme, manifest] = await Promise.all([
|
|
293
|
+
readmeEntry ? fetchRawFile(readmeEntry, fetchFn) : Promise.resolve(null),
|
|
294
|
+
pkgEntry
|
|
295
|
+
? fetchRawFile(pkgEntry, fetchFn).then((raw) =>
|
|
296
|
+
raw ? safeParseManifest(raw) : emptyManifest(),
|
|
297
|
+
)
|
|
298
|
+
: Promise.resolve(emptyManifest()),
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
return { manifest, readme };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** List a repo directory, returning `null` on 404 or any transient failure. */
|
|
305
|
+
async function listDirSafe(
|
|
306
|
+
owner: string,
|
|
307
|
+
repo: string,
|
|
308
|
+
apiPath: string,
|
|
309
|
+
ref: string,
|
|
310
|
+
fetchFn: FetchLike,
|
|
311
|
+
): Promise<readonly GitHubContentEntry[] | null> {
|
|
312
|
+
const suffix = apiPath
|
|
313
|
+
? `/${encodeURIComponent(apiPath).replaceAll("%2F", "/")}`
|
|
314
|
+
: "";
|
|
315
|
+
const url =
|
|
316
|
+
`https://api.github.com/repos/${owner}/${repo}/contents${suffix}` +
|
|
317
|
+
`?ref=${encodeURIComponent(ref)}`;
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const res = await githubFetch(url, "application/vnd.github+json", fetchFn);
|
|
321
|
+
if (!res.ok) return null;
|
|
322
|
+
const body = (await res.json()) as unknown;
|
|
323
|
+
if (!Array.isArray(body)) return null;
|
|
324
|
+
return body as readonly GitHubContentEntry[];
|
|
325
|
+
} catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Download a file entry's raw body, returning `null` on any failure. */
|
|
331
|
+
async function fetchRawFile(
|
|
332
|
+
entry: GitHubContentEntry,
|
|
333
|
+
fetchFn: FetchLike,
|
|
334
|
+
): Promise<string | null> {
|
|
335
|
+
if (!entry.download_url) return null;
|
|
336
|
+
try {
|
|
337
|
+
const res = await githubFetch(
|
|
338
|
+
entry.download_url,
|
|
339
|
+
"application/vnd.github.raw",
|
|
340
|
+
fetchFn,
|
|
341
|
+
);
|
|
342
|
+
if (!res.ok) return null;
|
|
343
|
+
return await res.text();
|
|
344
|
+
} catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function findMarketplaceEntry(
|
|
350
|
+
name: string,
|
|
351
|
+
ref: string,
|
|
352
|
+
fetchFn: FetchLike,
|
|
353
|
+
): Promise<MarketplaceEntry | null> {
|
|
354
|
+
try {
|
|
355
|
+
const entries = await fetchMarketplaceEntries({ fetch: fetchFn }, { ref });
|
|
356
|
+
return entries.find((e) => e.name === name) ?? null;
|
|
357
|
+
} catch {
|
|
358
|
+
// A missing or malformed manifest degrades to "no external entry" — the
|
|
359
|
+
// marketplace is supplementary, never required to render a detail view.
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function githubFetch(
|
|
365
|
+
url: string,
|
|
366
|
+
accept: string,
|
|
367
|
+
fetchFn: FetchLike,
|
|
368
|
+
): Promise<Response> {
|
|
369
|
+
return fetchFn(url, {
|
|
370
|
+
headers: { Accept: accept, "User-Agent": "vellum-assistant-cli" },
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function emptyManifest(): PluginManifestFields {
|
|
375
|
+
return { version: null, description: null, homepage: null, license: null };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function safeParseManifest(raw: string): PluginManifestFields {
|
|
379
|
+
try {
|
|
380
|
+
return parseManifest(raw);
|
|
381
|
+
} catch {
|
|
382
|
+
return emptyManifest();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Extract the surfaced fields from a `package.json` body. Throws on bad JSON. */
|
|
387
|
+
function parseManifest(raw: string): PluginManifestFields {
|
|
388
|
+
const parsed: unknown = JSON.parse(raw);
|
|
389
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
390
|
+
return emptyManifest();
|
|
391
|
+
}
|
|
392
|
+
const meta = parsed as Record<string, unknown>;
|
|
393
|
+
return {
|
|
394
|
+
version: typeof meta.version === "string" ? meta.version : null,
|
|
395
|
+
description: typeof meta.description === "string" ? meta.description : null,
|
|
396
|
+
homepage: typeof meta.homepage === "string" ? meta.homepage : null,
|
|
397
|
+
license: normalizeLicense(meta.license),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* `package.json#license` is usually an SPDX string but the legacy object form
|
|
403
|
+
* `{ "type": "MIT" }` still appears in the wild — surface its `type`.
|
|
404
|
+
*/
|
|
405
|
+
function normalizeLicense(value: unknown): string | null {
|
|
406
|
+
if (typeof value === "string") return value;
|
|
407
|
+
if (
|
|
408
|
+
typeof value === "object" &&
|
|
409
|
+
value !== null &&
|
|
410
|
+
"type" in value &&
|
|
411
|
+
typeof (value as { type: unknown }).type === "string"
|
|
412
|
+
) {
|
|
413
|
+
return (value as { type: string }).type;
|
|
414
|
+
}
|
|
415
|
+
return null;
|
|
416
|
+
}
|