From 690046637fa34c067d1890fa7c281c28e290c727 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Apr 2026 19:13:04 -0700 Subject: [PATCH] fix(web): resolve provider candidates from plugin registry --- src/plugins/web-content-extractors.runtime.ts | 21 +++++++-- src/plugins/web-provider-public-artifacts.ts | 12 ++++- ...web-provider-resolution-candidates.test.ts | 22 ++++++--- src/plugins/web-provider-resolution-shared.ts | 45 +++++++++++++------ src/plugins/web-search-credential-presence.ts | 11 ++++- 5 files changed, 83 insertions(+), 28 deletions(-) diff --git a/src/plugins/web-content-extractors.runtime.ts b/src/plugins/web-content-extractors.runtime.ts index ff056106331..1975792cad8 100644 --- a/src/plugins/web-content-extractors.runtime.ts +++ b/src/plugins/web-content-extractors.runtime.ts @@ -5,8 +5,9 @@ import { normalizePluginsConfig, resolveEffectivePluginActivationState, } from "./config-state.js"; -import { loadPluginManifestRegistry } from "./manifest-registry.js"; +import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js"; import type { PluginManifestRecord } from "./manifest-registry.js"; +import { loadPluginRegistrySnapshot } from "./plugin-registry.js"; import { loadBundledWebContentExtractorEntriesFromDir } from "./web-content-extractor-public-artifacts.js"; import type { PluginWebContentExtractorEntry } from "./web-content-extractor-types.js"; @@ -30,10 +31,17 @@ function resolveBundledWebContentExtractorCompatPluginIds(params: { }): string[] { const onlyPluginIdSet = params.onlyPluginIds && params.onlyPluginIds.length > 0 ? new Set(params.onlyPluginIds) : null; - return loadPluginManifestRegistry({ + const index = loadPluginRegistrySnapshot({ config: params.config, workspaceDir: params.workspaceDir, env: params.env, + }); + return loadPluginManifestRegistryForInstalledIndex({ + index, + config: params.config, + workspaceDir: params.workspaceDir, + env: params.env, + includeDisabled: true, }) .plugins.filter( (plugin) => @@ -74,10 +82,17 @@ function resolveEnabledBundledExtractorPlugins(params: { }); const onlyPluginIdSet = params.onlyPluginIds && params.onlyPluginIds.length > 0 ? new Set(params.onlyPluginIds) : null; - return loadPluginManifestRegistry({ + const index = loadPluginRegistrySnapshot({ config: activation.config, workspaceDir: params.workspaceDir, env: params.env, + }); + return loadPluginManifestRegistryForInstalledIndex({ + index, + config: activation.config, + workspaceDir: params.workspaceDir, + env: params.env, + includeDisabled: true, }).plugins.filter((plugin) => { if ( plugin.origin !== "bundled" || diff --git a/src/plugins/web-provider-public-artifacts.ts b/src/plugins/web-provider-public-artifacts.ts index d19b3f070fb..e0a2f1572fd 100644 --- a/src/plugins/web-provider-public-artifacts.ts +++ b/src/plugins/web-provider-public-artifacts.ts @@ -1,6 +1,7 @@ import path from "node:path"; import type { PluginLoadOptions } from "./loader.js"; -import { loadPluginManifestRegistry } from "./manifest-registry.js"; +import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js"; +import { loadPluginRegistrySnapshot } from "./plugin-registry.js"; import type { PluginWebFetchProviderEntry, PluginWebSearchProviderEntry } from "./types.js"; import { resolveBundledWebFetchResolutionConfig } from "./web-fetch-providers.shared.js"; import { @@ -56,11 +57,18 @@ function resolveBundledManifestRecordsByPluginId(params: { onlyPluginIds: readonly string[]; }) { const allowedPluginIds = new Set(params.onlyPluginIds); + const index = loadPluginRegistrySnapshot({ + config: params.config, + workspaceDir: params.workspaceDir, + env: params.env, + }); return new Map( - loadPluginManifestRegistry({ + loadPluginManifestRegistryForInstalledIndex({ + index, config: params.config, workspaceDir: params.workspaceDir, env: params.env, + includeDisabled: true, }) .plugins.filter((record) => record.origin === "bundled" && allowedPluginIds.has(record.id)) .map((record) => [record.id, record] as const), diff --git a/src/plugins/web-provider-resolution-candidates.test.ts b/src/plugins/web-provider-resolution-candidates.test.ts index cff5516bd1b..fd1c1b0b80b 100644 --- a/src/plugins/web-provider-resolution-candidates.test.ts +++ b/src/plugins/web-provider-resolution-candidates.test.ts @@ -1,12 +1,17 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ - loadPluginManifestRegistry: vi.fn(), + loadPluginRegistrySnapshot: vi.fn(), + loadPluginManifestRegistryForInstalledIndex: vi.fn(), })); -vi.mock("./manifest-registry.js", () => ({ - loadPluginManifestRegistry: (...args: unknown[]) => mocks.loadPluginManifestRegistry(...args), - resolveManifestContractPluginIds: vi.fn(), +vi.mock("./plugin-registry.js", () => ({ + loadPluginRegistrySnapshot: (...args: unknown[]) => mocks.loadPluginRegistrySnapshot(...args), +})); + +vi.mock("./manifest-registry-installed.js", () => ({ + loadPluginManifestRegistryForInstalledIndex: (...args: unknown[]) => + mocks.loadPluginManifestRegistryForInstalledIndex(...args), })); let resolveManifestDeclaredWebProviderCandidatePluginIds: typeof import("./web-provider-resolution-shared.js").resolveManifestDeclaredWebProviderCandidatePluginIds; @@ -18,8 +23,10 @@ describe("resolveManifestDeclaredWebProviderCandidatePluginIds", () => { }); beforeEach(() => { - mocks.loadPluginManifestRegistry.mockReset(); - mocks.loadPluginManifestRegistry.mockReturnValue({ + mocks.loadPluginRegistrySnapshot.mockReset(); + mocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] }); + mocks.loadPluginManifestRegistryForInstalledIndex.mockReset(); + mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({ plugins: [ { id: "alpha", @@ -69,6 +76,7 @@ describe("resolveManifestDeclaredWebProviderCandidatePluginIds", () => { configKey: "webSearch", }), ).toEqual(["alpha", "beta"]); - expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(1); + expect(mocks.loadPluginRegistrySnapshot).toHaveBeenCalledTimes(1); + expect(mocks.loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledTimes(1); }); }); diff --git a/src/plugins/web-provider-resolution-shared.ts b/src/plugins/web-provider-resolution-shared.ts index a1ee8831f94..c8b0ad17b08 100644 --- a/src/plugins/web-provider-resolution-shared.ts +++ b/src/plugins/web-provider-resolution-shared.ts @@ -1,10 +1,8 @@ import { resolveBundledPluginCompatibleLoadValues } from "./activation-context.js"; import type { PluginLoadOptions } from "./loader.js"; -import { - loadPluginManifestRegistry, - resolveManifestContractPluginIds, - type PluginManifestRecord, -} from "./manifest-registry.js"; +import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js"; +import type { PluginManifestRecord } from "./manifest-registry.js"; +import { loadPluginRegistrySnapshot } from "./plugin-registry.js"; import { createPluginIdScopeSet, normalizePluginIdScope, @@ -62,6 +60,25 @@ function pluginManifestDeclaresProviderConfig( return typeof properties === "object" && properties !== null && configKey in properties; } +function loadInstalledWebProviderManifestRecords(params: { + config?: PluginLoadOptions["config"]; + workspaceDir?: string; + env?: PluginLoadOptions["env"]; +}): readonly PluginManifestRecord[] { + const index = loadPluginRegistrySnapshot({ + config: params.config, + workspaceDir: params.workspaceDir, + env: params.env, + }); + return loadPluginManifestRegistryForInstalledIndex({ + index, + config: params.config, + workspaceDir: params.workspaceDir, + env: params.env, + includeDisabled: true, + }).plugins; +} + export function resolveManifestDeclaredWebProviderCandidatePluginIds(params: { contract: WebProviderContract; configKey: WebProviderConfigKey; @@ -73,12 +90,12 @@ export function resolveManifestDeclaredWebProviderCandidatePluginIds(params: { }): string[] | undefined { const scopedPluginIds = normalizePluginIdScope(params.onlyPluginIds); const onlyPluginIdSet = createPluginIdScopeSet(scopedPluginIds); - const ids = loadPluginManifestRegistry({ + const ids = loadInstalledWebProviderManifestRecords({ config: params.config, workspaceDir: params.workspaceDir, env: params.env, }) - .plugins.filter( + .filter( (plugin) => (!params.origin || plugin.origin === params.origin) && (!onlyPluginIdSet || onlyPluginIdSet.has(plugin.id)) && @@ -98,13 +115,13 @@ function resolveBundledWebProviderCompatPluginIds(params: { workspaceDir?: string; env?: PluginLoadOptions["env"]; }): string[] { - return resolveManifestContractPluginIds({ - contract: params.contract, - origin: "bundled", - config: params.config, - workspaceDir: params.workspaceDir, - env: params.env, - }); + return loadInstalledWebProviderManifestRecords(params) + .filter( + (plugin) => + plugin.origin === "bundled" && (plugin.contracts?.[params.contract]?.length ?? 0) > 0, + ) + .map((plugin) => plugin.id) + .toSorted((left, right) => left.localeCompare(right)); } export function resolveBundledWebProviderResolutionConfig(params: { diff --git a/src/plugins/web-search-credential-presence.ts b/src/plugins/web-search-credential-presence.ts index 78aeb76a36c..30de22f60dd 100644 --- a/src/plugins/web-search-credential-presence.ts +++ b/src/plugins/web-search-credential-presence.ts @@ -1,6 +1,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { loadPluginManifestRegistry } from "./manifest-registry.js"; +import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js"; import type { PluginManifestRecord } from "./manifest-registry.js"; +import { loadPluginRegistrySnapshot } from "./plugin-registry.js"; import { resolvePluginWebSearchProviders } from "./web-search-providers.runtime.js"; function hasConfiguredCredentialValue(value: unknown): boolean { @@ -43,9 +44,15 @@ function hasManifestWebSearchEnvCredentialCandidate(params: { if (!env) { return false; } - return loadPluginManifestRegistry({ + const index = loadPluginRegistrySnapshot({ config: params.config, env, + }); + return loadPluginManifestRegistryForInstalledIndex({ + index, + config: params.config, + env, + includeDisabled: true, }).plugins.some((plugin) => { if (params.origin && plugin.origin !== params.origin) { return false;