diff --git a/src/commands/channel-setup/discovery.test.ts b/src/commands/channel-setup/discovery.test.ts index 13eb61fa2cd..61579f5a3c1 100644 --- a/src/commands/channel-setup/discovery.test.ts +++ b/src/commands/channel-setup/discovery.test.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { PluginAutoEnableResult } from "../../config/plugin-auto-enable.js"; -const loadInstalledPluginIndex = vi.hoisted(() => vi.fn()); -const listInstalledPluginContributionIds = vi.hoisted(() => +const loadPluginRegistrySnapshot = vi.hoisted(() => vi.fn()); +const listPluginContributionIds = vi.hoisted(() => vi.fn((_index?: unknown, _contribution?: unknown, _options?: unknown): string[] => []), ); const listChannelPluginCatalogEntries = vi.hoisted(() => vi.fn((): unknown[] => [])); @@ -17,10 +17,9 @@ const applyPluginAutoEnable = vi.hoisted(() => ), ); -vi.mock("../../plugins/installed-plugin-index.js", () => ({ - loadInstalledPluginIndex: (...args: unknown[]) => loadInstalledPluginIndex(...args), - listInstalledPluginContributionIds: (index: unknown, contribution: unknown, options?: unknown) => - listInstalledPluginContributionIds(index, contribution, options), +vi.mock("../../plugins/plugin-registry.js", () => ({ + loadPluginRegistrySnapshot: (...args: unknown[]) => loadPluginRegistrySnapshot(...args), + listPluginContributionIds: (args: unknown) => listPluginContributionIds(args), })); vi.mock("../../config/plugin-auto-enable.js", () => ({ @@ -40,11 +39,11 @@ import { listManifestInstalledChannelIds, resolveChannelSetupEntries } from "./d describe("listManifestInstalledChannelIds", () => { beforeEach(() => { - loadInstalledPluginIndex.mockReset().mockReturnValue({ + loadPluginRegistrySnapshot.mockReset().mockReturnValue({ plugins: [], diagnostics: [], }); - listInstalledPluginContributionIds.mockReset().mockReturnValue([]); + listPluginContributionIds.mockReset().mockReturnValue([]); listChannelPluginCatalogEntries.mockReset().mockReturnValue([]); listChatChannels.mockReset().mockReturnValue([]); applyPluginAutoEnable.mockReset().mockImplementation(({ config }) => ({ @@ -67,11 +66,11 @@ describe("listManifestInstalledChannelIds", () => { slack: ["slack configured"], }, }); - loadInstalledPluginIndex.mockReturnValue({ + loadPluginRegistrySnapshot.mockReturnValue({ plugins: [{ pluginId: "slack", contributions: { channels: ["slack"] } }], diagnostics: [], }); - listInstalledPluginContributionIds.mockReturnValue(["slack"]); + listPluginContributionIds.mockReturnValue(["slack"]); const installedIds = listManifestInstalledChannelIds({ cfg: {} as never, @@ -83,19 +82,18 @@ describe("listManifestInstalledChannelIds", () => { config: {}, env: { OPENCLAW_HOME: "/tmp/home" }, }); - expect(loadInstalledPluginIndex).toHaveBeenCalledWith({ + expect(loadPluginRegistrySnapshot).toHaveBeenCalledWith({ config: autoEnabledConfig, workspaceDir: "/tmp/workspace", env: { OPENCLAW_HOME: "/tmp/home" }, }); - expect(listInstalledPluginContributionIds).toHaveBeenCalledWith( - { + expect(listPluginContributionIds).toHaveBeenCalledWith({ + index: { plugins: [{ pluginId: "slack", contributions: { channels: ["slack"] } }], diagnostics: [], }, - "channels", - undefined, - ); + contribution: "channels", + }); expect(installedIds).toEqual(new Set(["slack"])); }); diff --git a/src/commands/channel-setup/discovery.ts b/src/commands/channel-setup/discovery.ts index df682b8451c..d43f39d741a 100644 --- a/src/commands/channel-setup/discovery.ts +++ b/src/commands/channel-setup/discovery.ts @@ -8,9 +8,9 @@ import type { ChannelMeta } from "../../channels/plugins/types.public.js"; import { applyPluginAutoEnable } from "../../config/plugin-auto-enable.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { - listInstalledPluginContributionIds, - loadInstalledPluginIndex, -} from "../../plugins/installed-plugin-index.js"; + listPluginContributionIds, + loadPluginRegistrySnapshot, +} from "../../plugins/plugin-registry.js"; import type { ChannelChoice } from "../onboard-types.js"; import { listSetupDiscoveryChannelPluginCatalogEntries, @@ -50,13 +50,13 @@ export function listManifestInstalledChannelIds(params: { env: params.env ?? process.env, }).config; const workspaceDir = resolveWorkspaceDir(resolvedConfig, params.workspaceDir); - const index = loadInstalledPluginIndex({ + const index = loadPluginRegistrySnapshot({ config: resolvedConfig, workspaceDir, env: params.env ?? process.env, }); return new Set( - listInstalledPluginContributionIds(index, "channels").map( + listPluginContributionIds({ index, contribution: "channels" }).map( (channelId) => channelId as ChannelChoice, ), ); diff --git a/src/commands/models/list.provider-catalog.test.ts b/src/commands/models/list.provider-catalog.test.ts index 54e37ab89f4..22709fc1371 100644 --- a/src/commands/models/list.provider-catalog.test.ts +++ b/src/commands/models/list.provider-catalog.test.ts @@ -6,18 +6,19 @@ import { } from "./list.provider-catalog.js"; const providerDiscoveryMocks = vi.hoisted(() => ({ - loadInstalledPluginIndex: vi.fn(), + loadPluginRegistrySnapshot: vi.fn(), + resolvePluginContributionOwners: vi.fn(), + resolveProviderOwners: vi.fn(), resolveBundledProviderCompatPluginIds: vi.fn(), - resolveInstalledPluginContributionOwners: vi.fn(), resolveOwningPluginIdsForProvider: vi.fn(), resolvePluginDiscoveryProviders: vi.fn(), resolveProviderContractPluginIdsForProviderAlias: vi.fn(), })); -vi.mock("../../plugins/installed-plugin-index.js", () => ({ - loadInstalledPluginIndex: providerDiscoveryMocks.loadInstalledPluginIndex, - resolveInstalledPluginContributionOwners: - providerDiscoveryMocks.resolveInstalledPluginContributionOwners, +vi.mock("../../plugins/plugin-registry.js", () => ({ + loadPluginRegistrySnapshot: providerDiscoveryMocks.loadPluginRegistrySnapshot, + resolvePluginContributionOwners: providerDiscoveryMocks.resolvePluginContributionOwners, + resolveProviderOwners: providerDiscoveryMocks.resolveProviderOwners, })); vi.mock("../../plugins/providers.js", () => ({ @@ -113,20 +114,17 @@ const defaultProviders = [chutesProvider, moonshotProvider, openaiProvider]; describe("loadProviderCatalogModelsForList", () => { beforeEach(() => { vi.clearAllMocks(); - providerDiscoveryMocks.loadInstalledPluginIndex.mockReturnValue({ + providerDiscoveryMocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [], diagnostics: [], }); - providerDiscoveryMocks.resolveInstalledPluginContributionOwners.mockImplementation( - (_index: unknown, contribution: string, matches: (contributionId: string) => boolean) => { - if (contribution !== "providers") { - return []; - } - return defaultProviders - .filter((provider) => matches(provider.id)) - .map((provider) => provider.pluginId); - }, + providerDiscoveryMocks.resolveProviderOwners.mockImplementation( + ({ providerId }: { providerId: string }) => + defaultProviders + .filter((provider) => provider.id === providerId) + .map((provider) => provider.pluginId), ); + providerDiscoveryMocks.resolvePluginContributionOwners.mockReturnValue([]); providerDiscoveryMocks.resolveBundledProviderCompatPluginIds.mockReturnValue([ "chutes", "moonshot", @@ -198,7 +196,7 @@ describe("loadProviderCatalogModelsForList", () => { }), ).resolves.toEqual(["moonshot"]); - expect(providerDiscoveryMocks.loadInstalledPluginIndex).toHaveBeenCalledWith({ + expect(providerDiscoveryMocks.loadPluginRegistrySnapshot).toHaveBeenCalledWith({ config: baseParams.cfg, env: baseParams.env, }); @@ -206,11 +204,10 @@ describe("loadProviderCatalogModelsForList", () => { }); it("does not fall back to legacy manifest ownership for disabled installed-index owners", async () => { - providerDiscoveryMocks.resolveInstalledPluginContributionOwners + providerDiscoveryMocks.resolveProviderOwners .mockReturnValueOnce([]) - .mockReturnValueOnce([]) - .mockReturnValueOnce(["moonshot"]) - .mockReturnValueOnce([]); + .mockReturnValueOnce(["moonshot"]); + providerDiscoveryMocks.resolvePluginContributionOwners.mockReturnValue([]); await expect( resolveProviderCatalogPluginIdsForFilter({ @@ -280,7 +277,7 @@ describe("loadProviderCatalogModelsForList", () => { }); it("does not skip registry for non-bundled static catalog owners", async () => { - providerDiscoveryMocks.resolveInstalledPluginContributionOwners.mockReturnValueOnce([]); + providerDiscoveryMocks.resolveProviderOwners.mockReturnValueOnce([]); providerDiscoveryMocks.resolveOwningPluginIdsForProvider.mockReturnValueOnce([ "workspace-static-provider", ]); @@ -298,7 +295,7 @@ describe("loadProviderCatalogModelsForList", () => { }); it("recognizes bundled provider hook aliases before the unknown-provider short-circuit", async () => { - providerDiscoveryMocks.resolveInstalledPluginContributionOwners.mockReturnValueOnce([]); + providerDiscoveryMocks.resolveProviderOwners.mockReturnValueOnce([]); await expect( resolveProviderCatalogPluginIdsForFilter({ @@ -350,7 +347,7 @@ describe("loadProviderCatalogModelsForList", () => { }); it("keeps unknown provider filters eligible for early empty results", async () => { - providerDiscoveryMocks.resolveInstalledPluginContributionOwners.mockReturnValueOnce([]); + providerDiscoveryMocks.resolveProviderOwners.mockReturnValueOnce([]); await expect( resolveProviderCatalogPluginIdsForFilter({ diff --git a/src/commands/models/list.provider-catalog.ts b/src/commands/models/list.provider-catalog.ts index e15af721939..6783683413c 100644 --- a/src/commands/models/list.provider-catalog.ts +++ b/src/commands/models/list.provider-catalog.ts @@ -5,10 +5,11 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { formatErrorMessage } from "../../infra/errors.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; import { - type InstalledPluginIndex, - loadInstalledPluginIndex, - resolveInstalledPluginContributionOwners, -} from "../../plugins/installed-plugin-index.js"; + loadPluginRegistrySnapshot, + resolvePluginContributionOwners, + resolveProviderOwners, + type PluginRegistrySnapshot, +} from "../../plugins/plugin-registry.js"; import { groupPluginDiscoveryProvidersByOrder, normalizePluginDiscoveryResult, @@ -37,18 +38,27 @@ function providerMatchesFilter(params: { } function collectMatchingContributionOwners( - index: InstalledPluginIndex, + index: PluginRegistrySnapshot, contribution: "providers" | "cliBackends", providerFilter: string, options: { includeDisabled?: boolean } = {}, ): string[] { + if (contribution === "providers") { + return [ + ...resolveProviderOwners({ + index, + providerId: providerFilter, + includeDisabled: options.includeDisabled, + }), + ]; + } return [ - ...resolveInstalledPluginContributionOwners( + ...resolvePluginContributionOwners({ index, - contribution, - (contributionId) => normalizeProviderId(contributionId) === providerFilter, - options, - ), + contribution: "cliBackends", + matches: (contributionId) => normalizeProviderId(contributionId) === providerFilter, + includeDisabled: options.includeDisabled, + }), ]; } @@ -57,7 +67,7 @@ function resolveInstalledIndexPluginIdsForProviderFilter(params: { env?: NodeJS.ProcessEnv; providerFilter: string; }): string[] | undefined { - const index = loadInstalledPluginIndex({ + const index = loadPluginRegistrySnapshot({ config: params.cfg, env: params.env, }); diff --git a/src/plugins/plugin-registry.test.ts b/src/plugins/plugin-registry.test.ts new file mode 100644 index 00000000000..8d71c3197b1 --- /dev/null +++ b/src/plugins/plugin-registry.test.ts @@ -0,0 +1,179 @@ +import fs from "node:fs"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import type { PluginCandidate } from "./discovery.js"; +import { + getPluginRecord, + inspectPluginRegistry, + isPluginEnabled, + listPluginContributionIds, + listPluginRecords, + loadPluginRegistrySnapshot, + refreshPluginRegistry, + resolveChannelOwners, + resolveCliBackendOwners, + resolvePluginContributionOwners, + resolveProviderOwners, + resolveSetupProviderOwners, +} from "./plugin-registry.js"; +import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.js"; + +const tempDirs: string[] = []; + +afterEach(() => { + cleanupTrackedTempDirs(tempDirs); +}); + +function makeTempDir() { + return makeTrackedTempDir("openclaw-plugin-registry", tempDirs); +} + +function hermeticEnv(overrides: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv { + return { + OPENCLAW_BUNDLED_PLUGINS_DIR: undefined, + OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1", + OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE: "1", + OPENCLAW_VERSION: "2026.4.25", + VITEST: "true", + ...overrides, + }; +} + +function createCandidate(rootDir: string): PluginCandidate { + fs.writeFileSync( + path.join(rootDir, "index.ts"), + "throw new Error('runtime entry should not load while reading plugin registry');\n", + "utf8", + ); + fs.writeFileSync( + path.join(rootDir, "openclaw.plugin.json"), + JSON.stringify({ + id: "demo", + name: "Demo", + configSchema: { type: "object" }, + providers: ["demo"], + channels: ["demo-chat"], + cliBackends: ["demo-cli"], + setup: { + providers: [{ id: "demo-setup", envVars: ["DEMO_API_KEY"] }], + cliBackends: ["demo-setup-cli"], + }, + channelConfigs: { + "demo-chat": { + schema: { type: "object" }, + }, + }, + modelCatalog: { + providers: { + demo: { + models: [{ id: "demo-model" }], + }, + }, + }, + commandAliases: [{ name: "demo-command" }], + contracts: { + tools: ["demo-tool"], + }, + }), + "utf8", + ); + return { + idHint: "demo", + source: path.join(rootDir, "index.ts"), + rootDir, + origin: "global", + }; +} + +describe("plugin registry facade", () => { + it("resolves cold plugin records and contribution owners without loading runtime", () => { + const rootDir = makeTempDir(); + const candidate = createCandidate(rootDir); + const index = loadPluginRegistrySnapshot({ + candidates: [candidate], + env: hermeticEnv(), + }); + + expect(listPluginRecords({ index }).map((plugin) => plugin.pluginId)).toEqual(["demo"]); + expect(getPluginRecord({ index, pluginId: "demo" })).toMatchObject({ + pluginId: "demo", + enabled: true, + }); + expect(isPluginEnabled({ index, pluginId: "demo" })).toBe(true); + expect(listPluginContributionIds({ index, contribution: "providers" })).toEqual(["demo"]); + expect(resolveProviderOwners({ index, providerId: "demo" })).toEqual(["demo"]); + expect(resolveChannelOwners({ index, channelId: "demo-chat" })).toEqual(["demo"]); + expect(resolveCliBackendOwners({ index, cliBackendId: "demo-cli" })).toEqual(["demo"]); + expect( + resolvePluginContributionOwners({ + index, + contribution: "cliBackends", + matches: (contributionId) => contributionId === "demo-cli", + }), + ).toEqual(["demo"]); + expect(resolveSetupProviderOwners({ index, setupProviderId: "demo-setup" })).toEqual(["demo"]); + }); + + it("keeps disabled records inspectable while excluding owners by default", () => { + const rootDir = makeTempDir(); + const candidate = createCandidate(rootDir); + const index = loadPluginRegistrySnapshot({ + candidates: [candidate], + config: { + plugins: { + entries: { + demo: { + enabled: false, + }, + }, + }, + }, + env: hermeticEnv(), + }); + + expect(getPluginRecord({ index, pluginId: "demo" })).toMatchObject({ + pluginId: "demo", + enabled: false, + }); + expect(resolveProviderOwners({ index, providerId: "demo" })).toEqual([]); + expect(resolveProviderOwners({ index, providerId: "demo", includeDisabled: true })).toEqual([ + "demo", + ]); + }); + + it("exposes explicit persisted registry inspect and refresh operations", async () => { + const stateDir = makeTempDir(); + const pluginDir = path.join(stateDir, "plugins", "demo"); + fs.mkdirSync(pluginDir, { recursive: true }); + const candidate = createCandidate(pluginDir); + const env = hermeticEnv(); + + await expect( + inspectPluginRegistry({ stateDir, candidates: [candidate], env }), + ).resolves.toMatchObject({ + state: "missing", + refreshReasons: ["missing"], + persisted: null, + current: { + plugins: [expect.objectContaining({ pluginId: "demo" })], + }, + }); + + await refreshPluginRegistry({ + reason: "manual", + stateDir, + candidates: [candidate], + env, + }); + + await expect( + inspectPluginRegistry({ stateDir, candidates: [candidate], env }), + ).resolves.toMatchObject({ + state: "fresh", + refreshReasons: [], + persisted: { + plugins: [expect.objectContaining({ pluginId: "demo" })], + }, + }); + }); +}); diff --git a/src/plugins/plugin-registry.ts b/src/plugins/plugin-registry.ts new file mode 100644 index 00000000000..48a1cf57799 --- /dev/null +++ b/src/plugins/plugin-registry.ts @@ -0,0 +1,174 @@ +import { normalizeProviderId } from "../agents/provider-id.js"; +import type { + InstalledPluginIndexStoreInspection, + InstalledPluginIndexStoreOptions, +} from "./installed-plugin-index-store.js"; +import { + getInstalledPluginRecord, + isInstalledPluginEnabled, + listInstalledPluginContributionIds, + listInstalledPluginRecords, + loadInstalledPluginIndex, + resolveInstalledPluginContributionOwners, + type InstalledPluginContributionKey, + type InstalledPluginIndex, + type InstalledPluginIndexRecord, + type LoadInstalledPluginIndexParams, + type RefreshInstalledPluginIndexParams, +} from "./installed-plugin-index.js"; + +export type PluginRegistrySnapshot = InstalledPluginIndex; +export type PluginRegistryRecord = InstalledPluginIndexRecord; +export type PluginRegistryInspection = InstalledPluginIndexStoreInspection; + +export type LoadPluginRegistryParams = LoadInstalledPluginIndexParams & { + index?: PluginRegistrySnapshot; +}; + +export type PluginRegistryContributionOptions = LoadPluginRegistryParams & { + includeDisabled?: boolean; +}; + +export type GetPluginRecordParams = LoadPluginRegistryParams & { + pluginId: string; +}; + +export type ResolvePluginContributionOwnersParams = PluginRegistryContributionOptions & { + contribution: InstalledPluginContributionKey; + matches: string | ((contributionId: string) => boolean); +}; + +export type ListPluginContributionIdsParams = PluginRegistryContributionOptions & { + contribution: InstalledPluginContributionKey; +}; + +export type ResolveProviderOwnersParams = PluginRegistryContributionOptions & { + providerId: string; +}; + +export type ResolveChannelOwnersParams = PluginRegistryContributionOptions & { + channelId: string; +}; + +export type ResolveCliBackendOwnersParams = PluginRegistryContributionOptions & { + cliBackendId: string; +}; + +export type ResolveSetupProviderOwnersParams = PluginRegistryContributionOptions & { + setupProviderId: string; +}; + +function normalizeContributionId(value: string): string { + return value.trim(); +} + +function resolveSnapshot(params: LoadPluginRegistryParams = {}): PluginRegistrySnapshot { + return params.index ?? loadInstalledPluginIndex(params); +} + +export function loadPluginRegistrySnapshot( + params: LoadPluginRegistryParams = {}, +): PluginRegistrySnapshot { + return resolveSnapshot(params); +} + +export function listPluginRecords( + params: LoadPluginRegistryParams = {}, +): readonly PluginRegistryRecord[] { + return listInstalledPluginRecords(resolveSnapshot(params)); +} + +export function getPluginRecord(params: GetPluginRecordParams): PluginRegistryRecord | undefined { + return getInstalledPluginRecord(resolveSnapshot(params), params.pluginId); +} + +export function isPluginEnabled(params: GetPluginRecordParams): boolean { + return isInstalledPluginEnabled(resolveSnapshot(params), params.pluginId); +} + +export function listPluginContributionIds( + params: ListPluginContributionIdsParams, +): readonly string[] { + return listInstalledPluginContributionIds(resolveSnapshot(params), params.contribution, { + includeDisabled: params.includeDisabled, + }); +} + +export function resolvePluginContributionOwners( + params: ResolvePluginContributionOwnersParams, +): readonly string[] { + return resolveInstalledPluginContributionOwners( + resolveSnapshot(params), + params.contribution, + params.matches, + { + includeDisabled: params.includeDisabled, + }, + ); +} + +export function resolveProviderOwners(params: ResolveProviderOwnersParams): readonly string[] { + const providerId = normalizeProviderId(params.providerId); + if (!providerId) { + return []; + } + return resolvePluginContributionOwners({ + ...params, + contribution: "providers", + matches: (contributionId) => normalizeProviderId(contributionId) === providerId, + }); +} + +export function resolveChannelOwners(params: ResolveChannelOwnersParams): readonly string[] { + const channelId = normalizeContributionId(params.channelId); + if (!channelId) { + return []; + } + return resolvePluginContributionOwners({ + ...params, + contribution: "channels", + matches: channelId, + }); +} + +export function resolveCliBackendOwners(params: ResolveCliBackendOwnersParams): readonly string[] { + const cliBackendId = normalizeContributionId(params.cliBackendId); + if (!cliBackendId) { + return []; + } + return resolvePluginContributionOwners({ + ...params, + contribution: "cliBackends", + matches: cliBackendId, + }); +} + +export function resolveSetupProviderOwners( + params: ResolveSetupProviderOwnersParams, +): readonly string[] { + const setupProviderId = normalizeContributionId(params.setupProviderId); + if (!setupProviderId) { + return []; + } + return resolvePluginContributionOwners({ + ...params, + contribution: "setupProviders", + matches: setupProviderId, + }); +} + +export function inspectPluginRegistry( + params: LoadInstalledPluginIndexParams & InstalledPluginIndexStoreOptions = {}, +): Promise { + return import("./installed-plugin-index-store.js").then((store) => + store.inspectPersistedInstalledPluginIndex(params), + ); +} + +export function refreshPluginRegistry( + params: RefreshInstalledPluginIndexParams & InstalledPluginIndexStoreOptions, +): Promise { + return import("./installed-plugin-index-store.js").then((store) => + store.refreshPersistedInstalledPluginIndex(params), + ); +} diff --git a/src/plugins/provider-discovery.ts b/src/plugins/provider-discovery.ts index 6f668f15190..95361faa01d 100644 --- a/src/plugins/provider-discovery.ts +++ b/src/plugins/provider-discovery.ts @@ -2,11 +2,11 @@ import { normalizeProviderId } from "../agents/model-selection.js"; import type { ModelProviderConfig } from "../config/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { - listInstalledPluginContributionIds, - loadInstalledPluginIndex, - type InstalledPluginIndex, - type LoadInstalledPluginIndexParams, -} from "./installed-plugin-index.js"; + listPluginContributionIds, + loadPluginRegistrySnapshot, + type LoadPluginRegistryParams, + type PluginRegistrySnapshot, +} from "./plugin-registry.js"; import type { ProviderDiscoveryOrder, ProviderPlugin } from "./types.js"; const DISCOVERY_ORDER: readonly ProviderDiscoveryOrder[] = ["simple", "profile", "paired", "late"]; @@ -44,8 +44,8 @@ export type ResolveRuntimePluginDiscoveryProvidersParams = { discoveryEntriesOnly?: boolean; }; -export type ResolveInstalledPluginProviderContributionIdsParams = LoadInstalledPluginIndexParams & { - index?: InstalledPluginIndex; +export type ResolveInstalledPluginProviderContributionIdsParams = LoadPluginRegistryParams & { + index?: PluginRegistrySnapshot; includeDisabled?: boolean; }; @@ -56,9 +56,11 @@ function sortedValues(values: Iterable): string[] { export function resolveInstalledPluginProviderContributionIds( params: ResolveInstalledPluginProviderContributionIdsParams = {}, ): string[] { - const index = params.index ?? loadInstalledPluginIndex(params); + const index = params.index ?? loadPluginRegistrySnapshot(params); return sortedValues( - listInstalledPluginContributionIds(index, "providers", { + listPluginContributionIds({ + index, + contribution: "providers", includeDisabled: params.includeDisabled, }), );