@vellumai/assistant 0.9.0 → 0.10.0-dev.202606192226.8ed8a6a
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 +19 -35
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +28 -22
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +1128 -119
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
- package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/approval-cascade.test.ts +6 -0
- package/src/__tests__/approval-routes-http.test.ts +128 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compaction-events.test.ts +6 -0
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +268 -27
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-process-callsite.test.ts +6 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-queue.test.ts +6 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-slash-queue.test.ts +6 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +6 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +4 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -13
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-pressure-tools.test.ts +41 -1
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/document-tool-security.test.ts +9 -6
- package/src/__tests__/document-update-default-surface.test.ts +202 -0
- package/src/__tests__/dynamic-page-surface.test.ts +152 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/gemini-count-tokens.test.ts +70 -0
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/injector-disk-pressure.test.ts +10 -5
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +4 -7
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +30 -23
- package/src/__tests__/llm-resolver.test.ts +70 -24
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +49 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/secret-response-routing.test.ts +35 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +16 -2
- package/src/__tests__/skill-execute-input.test.ts +177 -1
- package/src/__tests__/skill-load-tool.test.ts +72 -0
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +266 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +8 -10
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +2 -3
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +75 -29
- package/src/api/README.md +6 -6
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +28 -4
- package/src/api/responses/home.ts +26 -4
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +183 -80
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/db/status.ts +37 -3
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +400 -14
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +31 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
- package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/install-from-github.ts +128 -19
- package/src/cli/lib/merge-plugin-tree.ts +328 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/plugin-marketplace.ts +11 -0
- package/src/cli/lib/plugin-pin-history.ts +257 -0
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +298 -10
- package/src/cli/program.ts +2 -6
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/document-editor/SKILL.md +3 -3
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
- package/src/config/bundled-skills/workflows/SKILL.md +14 -8
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +15 -2
- package/src/config/feature-flag-registry.json +46 -31
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/loader.ts +4 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +109 -44
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +89 -106
- package/src/contacts/contacts-write.ts +5 -22
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +88 -54
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +114 -22
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +53 -19
- package/src/daemon/conversation.ts +131 -20
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +15 -16
- package/src/daemon/handlers/config-slack-channel.ts +22 -3
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -27
- package/src/daemon/memory-v2-startup.test.ts +131 -0
- package/src/daemon/memory-v2-startup.ts +100 -1
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/daemon/wake-conversation-ops.ts +1 -0
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/home/relationship-state.ts +2 -4
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/db-logs-attach.test.ts +122 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-async-query.ts +20 -2
- package/src/memory/db-connection.ts +49 -5
- package/src/memory/db-init.ts +17 -17
- package/src/memory/embedding-backend.ts +38 -1
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +54 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +28 -227
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -5
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +58 -103
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +16 -3
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +7 -16
- package/src/notifications/notification-utils.ts +19 -20
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -121
- package/src/oauth/AGENTS.md +5 -24
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/permissions/secret-prompter.ts +15 -1
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +33 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/compaction/window-manager.ts +18 -0
- package/src/plugins/defaults/index.ts +60 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +14 -4
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/anthropic/client.ts +230 -175
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/call-site-routing.ts +10 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/gemini/client.ts +43 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/ratelimit.ts +9 -0
- package/src/providers/retry.ts +62 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/types.ts +16 -0
- package/src/providers/usage-tracking.ts +7 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/__tests__/agent-wake.test.ts +227 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +59 -63
- package/src/runtime/agent-wake.ts +71 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +22 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +3 -3
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +9 -25
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +126 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +35 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +383 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-routes.ts +40 -9
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +39 -14
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +366 -11
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/secret-routes.ts +15 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +229 -44
- package/src/runtime/routes/workflow-routes.ts +131 -29
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/inference-profile.ts +2 -14
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +17 -4
- package/src/subagent/types.ts +6 -0
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +30 -19
- package/src/tools/document/document-tool.ts +30 -6
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +4 -3
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +7 -12
- package/src/tools/schedule/update.ts +4 -11
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +99 -2
- package/src/tools/skills/load.ts +43 -2
- package/src/tools/subagent/spawn.ts +64 -15
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +18 -13
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/types.ts +10 -0
- package/src/tools/ui-surface/definitions.ts +64 -3
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/logs-db-path.ts +22 -0
- package/src/util/platform.ts +10 -3
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -28
- package/src/workflows/run-manager.ts +66 -24
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three-way merge of a plugin tree, used by `plugins upgrade --strategy` to
|
|
3
|
+
* carry local edits forward across an upgrade instead of discarding them.
|
|
4
|
+
*
|
|
5
|
+
* An upgrade has three inputs, exactly like a git merge:
|
|
6
|
+
* - **base** — the tree the plugin was installed at (the recorded commit,
|
|
7
|
+
* re-materialized through the install pipeline; see {@link ./diff-plugin}).
|
|
8
|
+
* - **ours** — the current on-disk install, carrying any local edits.
|
|
9
|
+
* - **theirs** — the marketplace's current pin, the tree being upgraded to.
|
|
10
|
+
*
|
|
11
|
+
* Per file the merge is delegated to `git merge-file`, the same line-level
|
|
12
|
+
* three-way algorithm git itself uses: a hunk edited on only one side is taken
|
|
13
|
+
* from that side, so non-conflicting edits from *both* sides survive. Only a
|
|
14
|
+
* hunk edited differently on both sides is a true conflict, resolved per the
|
|
15
|
+
* caller's strategy:
|
|
16
|
+
* - `ours` keeps the local hunk (`git merge-file --ours`),
|
|
17
|
+
* - `theirs` keeps the pinned hunk (`git merge-file --theirs`),
|
|
18
|
+
* - `assistant` writes standard git conflict markers into the file (no resolve
|
|
19
|
+
* flag) and reports the path, leaving the conflict for the assistant to
|
|
20
|
+
* resolve — exactly the mid-merge working-tree UX.
|
|
21
|
+
*
|
|
22
|
+
* File-level add/delete divergence (a file added, removed, or modified on only
|
|
23
|
+
* one side) is resolved here before any line merge, since `git merge-file`
|
|
24
|
+
* operates on three existing blobs.
|
|
25
|
+
*
|
|
26
|
+
* Binary files cannot be line-merged, so a binary file that diverged on both
|
|
27
|
+
* sides is resolved whole-file by the strategy (`ours`/`theirs`); under
|
|
28
|
+
* `assistant` the local copy is kept and the path is reported as a binary
|
|
29
|
+
* conflict, since markers cannot be written into binary content.
|
|
30
|
+
*
|
|
31
|
+
* The `overwrite` strategy never reaches here — it discards local edits and is
|
|
32
|
+
* a plain re-install at the pin.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { execFile } from "node:child_process";
|
|
36
|
+
import {
|
|
37
|
+
mkdirSync,
|
|
38
|
+
mkdtempSync,
|
|
39
|
+
readFileSync,
|
|
40
|
+
rmSync,
|
|
41
|
+
writeFileSync,
|
|
42
|
+
} from "node:fs";
|
|
43
|
+
import { tmpdir } from "node:os";
|
|
44
|
+
import { dirname, join } from "node:path";
|
|
45
|
+
import { promisify } from "node:util";
|
|
46
|
+
|
|
47
|
+
import { INSTALL_META_FILENAME } from "./install-from-github.js";
|
|
48
|
+
import { computeFingerprint } from "./plugin-fingerprint.js";
|
|
49
|
+
|
|
50
|
+
const execFileAsync = promisify(execFile);
|
|
51
|
+
|
|
52
|
+
/** Cap on a single `git merge-file`; a per-file line merge is near-instant. */
|
|
53
|
+
const MERGE_TIMEOUT_MS = 30_000;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* How hunks edited differently on both sides are reconciled. `ours`/`theirs`
|
|
57
|
+
* auto-resolve toward that side; `assistant` writes conflict markers and
|
|
58
|
+
* reports the conflict for later resolution. `overwrite` is not a merge
|
|
59
|
+
* strategy — the caller re-installs the pin wholesale instead of merging.
|
|
60
|
+
*/
|
|
61
|
+
export type PluginMergeStrategy = "ours" | "theirs" | "assistant";
|
|
62
|
+
|
|
63
|
+
/** Human-readable labels for the conflict markers `assistant` writes. */
|
|
64
|
+
export interface ConflictLabels {
|
|
65
|
+
readonly ours: string;
|
|
66
|
+
readonly base: string;
|
|
67
|
+
readonly theirs: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const DEFAULT_CONFLICT_LABELS: ConflictLabels = {
|
|
71
|
+
ours: "local edits",
|
|
72
|
+
base: "install baseline",
|
|
73
|
+
theirs: "upgrade pin",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Inputs for a three-way plugin-tree merge. */
|
|
77
|
+
export interface MergePluginTreeOptions {
|
|
78
|
+
/** Re-materialized install-commit tree (the merge base). */
|
|
79
|
+
readonly baseDir: string;
|
|
80
|
+
/** Current on-disk install, carrying local edits (`ours`). */
|
|
81
|
+
readonly oursDir: string;
|
|
82
|
+
/** Marketplace-pinned tree being upgraded to (`theirs`). */
|
|
83
|
+
readonly theirsDir: string;
|
|
84
|
+
/** Empty directory the merged tree is written into. */
|
|
85
|
+
readonly destDir: string;
|
|
86
|
+
/** How to resolve hunks edited differently on both sides. */
|
|
87
|
+
readonly strategy: PluginMergeStrategy;
|
|
88
|
+
/** Marker labels for the `assistant` strategy. Defaults are used when omitted. */
|
|
89
|
+
readonly conflictLabels?: ConflictLabels;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Outcome of a three-way plugin-tree merge. */
|
|
93
|
+
export interface PluginMergeResult {
|
|
94
|
+
/** Number of files written into the destination tree. */
|
|
95
|
+
readonly fileCount: number;
|
|
96
|
+
/**
|
|
97
|
+
* Paths (relative to the tree root) left for the assistant to resolve.
|
|
98
|
+
* Text files carry git conflict markers; modify/delete divergences keep the
|
|
99
|
+
* surviving content. Empty for `ours`/`theirs`, which auto-resolve.
|
|
100
|
+
*/
|
|
101
|
+
readonly conflicts: readonly string[];
|
|
102
|
+
/**
|
|
103
|
+
* Paths of binary files that diverged on both sides. The local copy is kept
|
|
104
|
+
* (markers cannot be written into binary content), so the assistant must
|
|
105
|
+
* choose a version rather than edit markers. Empty for `ours`/`theirs`.
|
|
106
|
+
*/
|
|
107
|
+
readonly binaryConflicts: readonly string[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Result of merging a single file. */
|
|
111
|
+
interface MergedFile {
|
|
112
|
+
readonly content: Buffer;
|
|
113
|
+
/** Conflict markers were written into `content` (text, `assistant` only). */
|
|
114
|
+
readonly conflicted: boolean;
|
|
115
|
+
/** A binary file conflicted; `content` is the kept local copy (`assistant`). */
|
|
116
|
+
readonly binaryConflicted: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** A NUL byte in the leading bytes is the heuristic git uses to flag a blob as binary. */
|
|
120
|
+
function isBinary(buf: Buffer): boolean {
|
|
121
|
+
const len = Math.min(buf.length, 8000);
|
|
122
|
+
for (let i = 0; i < len; i++) {
|
|
123
|
+
if (buf[i] === 0) return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Write `content` to `destDir/rel`, creating parent directories as needed. */
|
|
129
|
+
function writeInto(destDir: string, rel: string, content: Buffer): void {
|
|
130
|
+
const abs = join(destDir, rel);
|
|
131
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
132
|
+
writeFileSync(abs, content);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Line-merge three blobs with `git merge-file`. Non-conflicting hunks from both
|
|
137
|
+
* sides are always kept. `ours`/`theirs` auto-resolve conflicting hunks toward
|
|
138
|
+
* that side (no markers); `assistant` writes git conflict markers and flags the
|
|
139
|
+
* file as conflicted.
|
|
140
|
+
*
|
|
141
|
+
* Binary input cannot be line-merged: a side that matches the base did not
|
|
142
|
+
* change, so the other side's edit is taken. A binary blob changed on *both*
|
|
143
|
+
* sides is a true conflict — resolved whole-file by `ours`/`theirs`, or kept as
|
|
144
|
+
* the local copy and flagged under `assistant` (no markers possible).
|
|
145
|
+
*/
|
|
146
|
+
async function threeWayMergeFile(
|
|
147
|
+
ours: Buffer,
|
|
148
|
+
base: Buffer,
|
|
149
|
+
theirs: Buffer,
|
|
150
|
+
strategy: PluginMergeStrategy,
|
|
151
|
+
labels: ConflictLabels,
|
|
152
|
+
): Promise<MergedFile> {
|
|
153
|
+
if (isBinary(ours) || isBinary(base) || isBinary(theirs)) {
|
|
154
|
+
if (ours.equals(base))
|
|
155
|
+
return { content: theirs, conflicted: false, binaryConflicted: false };
|
|
156
|
+
if (theirs.equals(base))
|
|
157
|
+
return { content: ours, conflicted: false, binaryConflicted: false };
|
|
158
|
+
if (strategy === "assistant") {
|
|
159
|
+
return { content: ours, conflicted: false, binaryConflicted: true };
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
content: strategy === "ours" ? ours : theirs,
|
|
163
|
+
conflicted: false,
|
|
164
|
+
binaryConflicted: false,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const scratch = mkdtempSync(join(tmpdir(), "plugin-merge-file-"));
|
|
169
|
+
try {
|
|
170
|
+
const oursPath = join(scratch, "ours");
|
|
171
|
+
const basePath = join(scratch, "base");
|
|
172
|
+
const theirsPath = join(scratch, "theirs");
|
|
173
|
+
writeFileSync(oursPath, ours);
|
|
174
|
+
writeFileSync(basePath, base);
|
|
175
|
+
writeFileSync(theirsPath, theirs);
|
|
176
|
+
|
|
177
|
+
// `-p` prints the merged result to stdout instead of editing `ours` in
|
|
178
|
+
// place. `--ours`/`--theirs` auto-resolve conflicting hunks toward that
|
|
179
|
+
// side; with neither (the `assistant` strategy) git writes conflict
|
|
180
|
+
// markers, labelled via `-L` so the assistant can tell the sides apart.
|
|
181
|
+
const args = ["merge-file", "-p"];
|
|
182
|
+
if (strategy === "ours" || strategy === "theirs") {
|
|
183
|
+
args.push(`--${strategy}`);
|
|
184
|
+
} else {
|
|
185
|
+
args.push("-L", labels.ours, "-L", labels.base, "-L", labels.theirs);
|
|
186
|
+
}
|
|
187
|
+
args.push(oursPath, basePath, theirsPath);
|
|
188
|
+
try {
|
|
189
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
190
|
+
cwd: scratch,
|
|
191
|
+
encoding: "buffer",
|
|
192
|
+
timeout: MERGE_TIMEOUT_MS,
|
|
193
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
194
|
+
});
|
|
195
|
+
return { content: stdout, conflicted: false, binaryConflicted: false };
|
|
196
|
+
} catch (err) {
|
|
197
|
+
// `git merge-file` exits with the (positive) conflict count when markers
|
|
198
|
+
// were left, still printing the full merged bytes to stdout — that is the
|
|
199
|
+
// only rejection we may install. A killed process (timeout) or a
|
|
200
|
+
// `maxBuffer` overflow also rejects here, but with truncated/partial
|
|
201
|
+
// stdout we must NOT install it, or a large conflicted file would be
|
|
202
|
+
// silently corrupted; surface those as errors. A resolving flag never
|
|
203
|
+
// leaves conflicts, so any non-zero exit there is likewise a real
|
|
204
|
+
// failure.
|
|
205
|
+
const e = err as {
|
|
206
|
+
stdout?: Buffer;
|
|
207
|
+
code?: unknown;
|
|
208
|
+
killed?: boolean;
|
|
209
|
+
signal?: string | null;
|
|
210
|
+
};
|
|
211
|
+
const isConflictExit =
|
|
212
|
+
strategy === "assistant" &&
|
|
213
|
+
e.killed !== true &&
|
|
214
|
+
e.signal == null &&
|
|
215
|
+
typeof e.code === "number" &&
|
|
216
|
+
e.code > 0;
|
|
217
|
+
if (isConflictExit && Buffer.isBuffer(e.stdout)) {
|
|
218
|
+
return { content: e.stdout, conflicted: true, binaryConflicted: false };
|
|
219
|
+
}
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
} finally {
|
|
223
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Three-way merge `oursDir`/`theirsDir` against `baseDir` into `destDir` per
|
|
229
|
+
* `strategy`. The provenance sidecar is excluded on every side — it is
|
|
230
|
+
* rewritten by the caller after the swap and must not be carried through the
|
|
231
|
+
* merge.
|
|
232
|
+
*
|
|
233
|
+
* Returns the file count plus, for the `assistant` strategy, the paths left for
|
|
234
|
+
* the assistant to resolve (text files with conflict markers and modify/delete
|
|
235
|
+
* divergences) and the binary files that conflicted.
|
|
236
|
+
*/
|
|
237
|
+
export async function mergePluginTree({
|
|
238
|
+
baseDir,
|
|
239
|
+
oursDir,
|
|
240
|
+
theirsDir,
|
|
241
|
+
destDir,
|
|
242
|
+
strategy,
|
|
243
|
+
conflictLabels,
|
|
244
|
+
}: MergePluginTreeOptions): Promise<PluginMergeResult> {
|
|
245
|
+
const labels = conflictLabels ?? DEFAULT_CONFLICT_LABELS;
|
|
246
|
+
const exclude = [INSTALL_META_FILENAME];
|
|
247
|
+
const base = computeFingerprint(baseDir, exclude).files;
|
|
248
|
+
const ours = computeFingerprint(oursDir, exclude).files;
|
|
249
|
+
const theirs = computeFingerprint(theirsDir, exclude).files;
|
|
250
|
+
|
|
251
|
+
const paths = new Set([
|
|
252
|
+
...Object.keys(base),
|
|
253
|
+
...Object.keys(ours),
|
|
254
|
+
...Object.keys(theirs),
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
const readBase = (rel: string) => readFileSync(join(baseDir, rel));
|
|
258
|
+
const readOurs = (rel: string) => readFileSync(join(oursDir, rel));
|
|
259
|
+
const readTheirs = (rel: string) => readFileSync(join(theirsDir, rel));
|
|
260
|
+
|
|
261
|
+
let fileCount = 0;
|
|
262
|
+
const conflicts: string[] = [];
|
|
263
|
+
const binaryConflicts: string[] = [];
|
|
264
|
+
const keep = (rel: string, content: Buffer): void => {
|
|
265
|
+
writeInto(destDir, rel, content);
|
|
266
|
+
fileCount++;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
for (const rel of paths) {
|
|
270
|
+
const b = base[rel];
|
|
271
|
+
const o = ours[rel];
|
|
272
|
+
const t = theirs[rel];
|
|
273
|
+
|
|
274
|
+
if (o !== undefined && t !== undefined) {
|
|
275
|
+
// Present on both sides: identical needs no merge; otherwise line-merge
|
|
276
|
+
// (an empty base when the file was added on both sides).
|
|
277
|
+
if (o === t) {
|
|
278
|
+
keep(rel, readOurs(rel));
|
|
279
|
+
} else {
|
|
280
|
+
const merged = await threeWayMergeFile(
|
|
281
|
+
readOurs(rel),
|
|
282
|
+
b !== undefined ? readBase(rel) : Buffer.alloc(0),
|
|
283
|
+
readTheirs(rel),
|
|
284
|
+
strategy,
|
|
285
|
+
labels,
|
|
286
|
+
);
|
|
287
|
+
keep(rel, merged.content);
|
|
288
|
+
if (merged.conflicted) conflicts.push(rel);
|
|
289
|
+
if (merged.binaryConflicted) binaryConflicts.push(rel);
|
|
290
|
+
}
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (o !== undefined) {
|
|
295
|
+
// Present only locally. A local-only addition (no base) always survives.
|
|
296
|
+
// A file deleted upstream is a delete: drop it when unchanged locally.
|
|
297
|
+
// On a modify/delete conflict `ours` keeps the edit and `theirs` honors
|
|
298
|
+
// the deletion; `assistant` keeps the edit and flags it (a whole-file
|
|
299
|
+
// conflict markers can't express).
|
|
300
|
+
if (b === undefined || (b !== o && strategy !== "theirs")) {
|
|
301
|
+
keep(rel, readOurs(rel));
|
|
302
|
+
if (b !== undefined && b !== o && strategy === "assistant") {
|
|
303
|
+
conflicts.push(rel);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (t !== undefined) {
|
|
310
|
+
// Present only at the pin. A remote-only addition (no base) always lands.
|
|
311
|
+
// A file deleted locally is a delete: keep upstream's removal when the
|
|
312
|
+
// pin left it unchanged. On a delete/modify conflict `theirs` keeps the
|
|
313
|
+
// pin's edit and `ours` honors the local deletion; `assistant` keeps the
|
|
314
|
+
// pin's edit and flags it.
|
|
315
|
+
if (b === undefined || (b !== t && strategy !== "ours")) {
|
|
316
|
+
keep(rel, readTheirs(rel));
|
|
317
|
+
if (b !== undefined && b !== t && strategy === "assistant") {
|
|
318
|
+
conflicts.push(rel);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Present only in the base: removed on both sides, so it stays removed.
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return { fileCount, conflicts, binaryConflicts };
|
|
328
|
+
}
|
|
@@ -120,6 +120,20 @@ export function compareFingerprint(
|
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Whether two fingerprints cover the same files with identical digests. Used to
|
|
125
|
+
* confirm a re-materialized tree faithfully reproduces a recorded baseline
|
|
126
|
+
* before it is trusted as a merge base.
|
|
127
|
+
*/
|
|
128
|
+
export function fingerprintsEqual(a: Fingerprint, b: Fingerprint): boolean {
|
|
129
|
+
const aKeys = Object.keys(a.files);
|
|
130
|
+
if (aKeys.length !== Object.keys(b.files).length) return false;
|
|
131
|
+
for (const key of aKeys) {
|
|
132
|
+
if (a.files[key] !== b.files[key]) return false;
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
123
137
|
/**
|
|
124
138
|
* Parse a fingerprint from already-decoded JSON. Lenient by design — any shape
|
|
125
139
|
* problem yields `null` so an older or hand-edited sidecar simply reports "no
|
|
@@ -35,6 +35,17 @@ const MARKETPLACE_SOURCE_OWNER = "vellum-ai";
|
|
|
35
35
|
const MARKETPLACE_SOURCE_REPO = "vellum-assistant";
|
|
36
36
|
const MARKETPLACE_FILE_PATH = "plugins/marketplace.json";
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Canonical GitHub coordinates of the marketplace manifest, exported so
|
|
40
|
+
* sibling readers (e.g. {@link ./plugin-pin-history}) resolve the same
|
|
41
|
+
* `owner/repo:path` rather than re-declaring it.
|
|
42
|
+
*/
|
|
43
|
+
export const MARKETPLACE_MANIFEST_LOCATION = {
|
|
44
|
+
owner: MARKETPLACE_SOURCE_OWNER,
|
|
45
|
+
repo: MARKETPLACE_SOURCE_REPO,
|
|
46
|
+
path: MARKETPLACE_FILE_PATH,
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
38
49
|
// ---------------------------------------------------------------------------
|
|
39
50
|
// Manifest schema (subset of the Claude Code marketplace schema)
|
|
40
51
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconstruct the history of marketplace pins for a single plugin.
|
|
3
|
+
*
|
|
4
|
+
* The curated `plugins/marketplace.json` manifest (see {@link ./plugin-marketplace})
|
|
5
|
+
* records only the *current* pin for each plugin — there is no stored version
|
|
6
|
+
* list. But the manifest is a file in a Git repo, and every time a curator bumps
|
|
7
|
+
* a plugin's pin, that is a commit to the manifest. So the history of reviewed
|
|
8
|
+
* pins already exists as the commit history of that one file: this module walks
|
|
9
|
+
* it (newest → oldest) via the GitHub API and reports the distinct pins a plugin
|
|
10
|
+
* has been promoted to over time, each tagged with the marketplace commit an
|
|
11
|
+
* install can reproduce it from.
|
|
12
|
+
*
|
|
13
|
+
* Why this is the safe source for "install an older version": every commit on
|
|
14
|
+
* the default branch was reviewed and merged, so a pin drawn from this history
|
|
15
|
+
* is a reviewed, known-good revision — unlike an arbitrary caller-supplied ref,
|
|
16
|
+
* which the install route deliberately refuses. Resolving an older pin to the
|
|
17
|
+
* marketplace commit that introduced it (rather than the plugin SHA directly)
|
|
18
|
+
* also means the install reads that era's manifest, so the curated adapter stub
|
|
19
|
+
* matches the pin and the historical version reproduces coherently.
|
|
20
|
+
*
|
|
21
|
+
* Designed for direct programmatic use with an injected `fetch`, mirroring the
|
|
22
|
+
* sibling plugin libraries.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { type FetchLike, sanitizePluginName } from "./install-from-github.js";
|
|
26
|
+
import {
|
|
27
|
+
fetchMarketplaceEntries,
|
|
28
|
+
MARKETPLACE_MANIFEST_LOCATION,
|
|
29
|
+
} from "./plugin-marketplace.js";
|
|
30
|
+
|
|
31
|
+
/** Default number of distinct historical pins surfaced by `plugins versions`. */
|
|
32
|
+
export const DEFAULT_PIN_HISTORY_LIMIT = 5;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Cap on how many manifest-touching commits are inspected in one walk. Bounds
|
|
36
|
+
* the GitHub API cost: a plugin whose pin changes rarely could otherwise force
|
|
37
|
+
* a fetch of the manifest at every commit that touched it for *any* plugin.
|
|
38
|
+
* Well above {@link DEFAULT_PIN_HISTORY_LIMIT} so the default listing is
|
|
39
|
+
* complete in all but pathological histories.
|
|
40
|
+
*/
|
|
41
|
+
const MAX_MARKETPLACE_COMMITS_SCANNED = 100;
|
|
42
|
+
|
|
43
|
+
/** A single point in a plugin's marketplace-pin history. */
|
|
44
|
+
export interface PluginPinHistoryEntry {
|
|
45
|
+
/** Plugin commit SHA pinned at this point in history. */
|
|
46
|
+
readonly pin: string;
|
|
47
|
+
/**
|
|
48
|
+
* Marketplace-manifest commit to install this pin from — the newest commit
|
|
49
|
+
* that carries it. Installing with this as the marketplace ref reproduces the
|
|
50
|
+
* pin together with the curated adapter stub of the same era.
|
|
51
|
+
*/
|
|
52
|
+
readonly marketplaceCommit: string;
|
|
53
|
+
/**
|
|
54
|
+
* ISO-8601 committer date (UTC) of {@link PluginPinHistoryEntry.marketplaceCommit},
|
|
55
|
+
* i.e. when this pin was promoted; `null` when the date could not be read.
|
|
56
|
+
*/
|
|
57
|
+
readonly promotedAt: string | null;
|
|
58
|
+
/** True for the pin currently active on the default branch. */
|
|
59
|
+
readonly current: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Options controlling the pin-history walk. */
|
|
63
|
+
export interface ListPinHistoryOptions {
|
|
64
|
+
/** Max distinct pins to return (newest first). Defaults to {@link DEFAULT_PIN_HISTORY_LIMIT}. */
|
|
65
|
+
readonly limit?: number;
|
|
66
|
+
/** Marketplace branch to read history from. Defaults to `main`. */
|
|
67
|
+
readonly ref?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Dependencies injected by the caller. */
|
|
71
|
+
export interface ListPinHistoryDeps {
|
|
72
|
+
/** HTTP client. Production callers pass `globalThis.fetch.bind(globalThis)`. */
|
|
73
|
+
readonly fetch: FetchLike;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Shape of a commit entry from the GitHub commits API. */
|
|
77
|
+
interface GitHubCommitListEntry {
|
|
78
|
+
readonly sha: string;
|
|
79
|
+
readonly commit: {
|
|
80
|
+
readonly committer?: { readonly date?: string } | null;
|
|
81
|
+
} | null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A manifest-touching commit and its committer date. */
|
|
85
|
+
interface ManifestCommit {
|
|
86
|
+
readonly sha: string;
|
|
87
|
+
readonly date: string | null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The pin-history walk could not read the marketplace commit list. */
|
|
91
|
+
export class PluginPinHistoryError extends Error {
|
|
92
|
+
constructor(message: string) {
|
|
93
|
+
super(message);
|
|
94
|
+
this.name = "PluginPinHistoryError";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* List the commits that touched the marketplace manifest on `ref`, newest
|
|
100
|
+
* first, capped at {@link MAX_MARKETPLACE_COMMITS_SCANNED}. A 404 (the file
|
|
101
|
+
* never existed at this ref) yields an empty list.
|
|
102
|
+
*/
|
|
103
|
+
async function listManifestCommits(
|
|
104
|
+
ref: string,
|
|
105
|
+
fetchFn: FetchLike,
|
|
106
|
+
): Promise<ManifestCommit[]> {
|
|
107
|
+
const { owner, repo, path } = MARKETPLACE_MANIFEST_LOCATION;
|
|
108
|
+
const url =
|
|
109
|
+
`https://api.github.com/repos/${owner}/${repo}/commits` +
|
|
110
|
+
`?path=${encodeURIComponent(path)}` +
|
|
111
|
+
`&sha=${encodeURIComponent(ref)}` +
|
|
112
|
+
`&per_page=${MAX_MARKETPLACE_COMMITS_SCANNED}`;
|
|
113
|
+
|
|
114
|
+
const res = await fetchFn(url, {
|
|
115
|
+
headers: {
|
|
116
|
+
Accept: "application/vnd.github+json",
|
|
117
|
+
"User-Agent": "vellum-assistant-cli",
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
if (res.status === 404) return [];
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
throw new PluginPinHistoryError(
|
|
123
|
+
`Marketplace commit history fetch failed for ${path} @ ${ref}: HTTP ${res.status}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let body: unknown;
|
|
128
|
+
try {
|
|
129
|
+
body = JSON.parse(await res.text());
|
|
130
|
+
} catch (err) {
|
|
131
|
+
throw new PluginPinHistoryError(
|
|
132
|
+
`Marketplace commit history is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (!Array.isArray(body)) {
|
|
136
|
+
throw new PluginPinHistoryError(
|
|
137
|
+
"Marketplace commit history response was not an array",
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const commits: ManifestCommit[] = [];
|
|
142
|
+
for (const item of body as GitHubCommitListEntry[]) {
|
|
143
|
+
if (typeof item?.sha !== "string") continue;
|
|
144
|
+
const rawDate = item.commit?.committer?.date;
|
|
145
|
+
commits.push({
|
|
146
|
+
sha: item.sha,
|
|
147
|
+
date: typeof rawDate === "string" ? rawDate : null,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return commits;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Resolve the plugin's pinned SHA in the manifest at a specific marketplace
|
|
155
|
+
* commit, or `null` when no entry claims the name at that revision.
|
|
156
|
+
*/
|
|
157
|
+
async function pinAtCommit(
|
|
158
|
+
name: string,
|
|
159
|
+
marketplaceCommit: string,
|
|
160
|
+
fetchFn: FetchLike,
|
|
161
|
+
): Promise<string | null> {
|
|
162
|
+
const entries = await fetchMarketplaceEntries(
|
|
163
|
+
{ fetch: fetchFn },
|
|
164
|
+
{ ref: marketplaceCommit },
|
|
165
|
+
);
|
|
166
|
+
return entries.find((e) => e.name === name)?.source.ref ?? null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Walk a plugin's distinct marketplace pins, newest first. Each pin is yielded
|
|
171
|
+
* once, tagged with the newest marketplace commit carrying it. Revisions where
|
|
172
|
+
* the plugin is absent from the manifest are skipped (a gap, not a pin). The
|
|
173
|
+
* `current` flag is set against the pin live on `ref` today.
|
|
174
|
+
*
|
|
175
|
+
* Bounded by {@link MAX_MARKETPLACE_COMMITS_SCANNED} commits scanned.
|
|
176
|
+
*/
|
|
177
|
+
async function* iteratePinHistory(
|
|
178
|
+
name: string,
|
|
179
|
+
ref: string,
|
|
180
|
+
fetchFn: FetchLike,
|
|
181
|
+
): AsyncGenerator<PluginPinHistoryEntry> {
|
|
182
|
+
const commits = await listManifestCommits(ref, fetchFn);
|
|
183
|
+
if (commits.length === 0) return;
|
|
184
|
+
|
|
185
|
+
// The pin live on the branch today, used to flag the current entry. Reading
|
|
186
|
+
// it explicitly (rather than assuming the newest commit carries it) stays
|
|
187
|
+
// correct even if the plugin was just removed from the manifest at the tip.
|
|
188
|
+
const currentPin = await pinAtCommit(name, ref, fetchFn);
|
|
189
|
+
|
|
190
|
+
let lastPin: string | null = null;
|
|
191
|
+
for (const commit of commits) {
|
|
192
|
+
const pin = await pinAtCommit(name, commit.sha, fetchFn);
|
|
193
|
+
if (pin === null) continue;
|
|
194
|
+
if (pin === lastPin) continue;
|
|
195
|
+
lastPin = pin;
|
|
196
|
+
yield {
|
|
197
|
+
pin,
|
|
198
|
+
marketplaceCommit: commit.sha,
|
|
199
|
+
promotedAt: commit.date,
|
|
200
|
+
current: currentPin !== null && pin === currentPin,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* List a plugin's distinct marketplace pins, newest first, capped at
|
|
207
|
+
* `limit` (default {@link DEFAULT_PIN_HISTORY_LIMIT}). The first entry is the
|
|
208
|
+
* current pin. An empty list means the plugin has no resolvable history at this
|
|
209
|
+
* ref (never in the manifest, or the manifest does not exist).
|
|
210
|
+
*
|
|
211
|
+
* Throws {@link PluginPinHistoryError} when the commit list cannot be read, and
|
|
212
|
+
* propagates a `MarketplaceFetchError` if a manifest revision fails to parse.
|
|
213
|
+
*/
|
|
214
|
+
export async function listPinHistory(
|
|
215
|
+
name: string,
|
|
216
|
+
deps: ListPinHistoryDeps,
|
|
217
|
+
opts: ListPinHistoryOptions = {},
|
|
218
|
+
): Promise<PluginPinHistoryEntry[]> {
|
|
219
|
+
const sanitized = sanitizePluginName(name);
|
|
220
|
+
const ref = opts.ref ?? "main";
|
|
221
|
+
const limit = opts.limit ?? DEFAULT_PIN_HISTORY_LIMIT;
|
|
222
|
+
|
|
223
|
+
const out: PluginPinHistoryEntry[] = [];
|
|
224
|
+
for await (const entry of iteratePinHistory(sanitized, ref, deps.fetch)) {
|
|
225
|
+
out.push(entry);
|
|
226
|
+
if (out.length >= limit) break;
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Resolve a plugin pin (a full commit SHA) to the marketplace commit it should
|
|
233
|
+
* be installed from, by searching the plugin's pin history. Returns `null` when
|
|
234
|
+
* the pin is not found in the scanned history — i.e. it is not a reviewed pin
|
|
235
|
+
* this plugin was ever promoted to (or it predates the scan window).
|
|
236
|
+
*
|
|
237
|
+
* Case-insensitive on the SHA, matching the rest of the install pipeline. The
|
|
238
|
+
* search walks the full scan window, not just the `limit` shown by
|
|
239
|
+
* {@link listPinHistory}, so a pin older than the default listing still
|
|
240
|
+
* resolves.
|
|
241
|
+
*/
|
|
242
|
+
export async function resolvePinToMarketplaceCommit(
|
|
243
|
+
name: string,
|
|
244
|
+
pin: string,
|
|
245
|
+
deps: ListPinHistoryDeps,
|
|
246
|
+
opts: { readonly ref?: string } = {},
|
|
247
|
+
): Promise<PluginPinHistoryEntry | null> {
|
|
248
|
+
const sanitized = sanitizePluginName(name);
|
|
249
|
+
const ref = opts.ref ?? "main";
|
|
250
|
+
const target = pin.trim().toLowerCase();
|
|
251
|
+
if (target.length === 0) return null;
|
|
252
|
+
|
|
253
|
+
for await (const entry of iteratePinHistory(sanitized, ref, deps.fetch)) {
|
|
254
|
+
if (entry.pin.toLowerCase() === target) return entry;
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|