fix: reuse provider discovery plugin metadata

This commit is contained in:
Shakker
2026-04-27 10:32:06 +01:00
parent c9e6f371e4
commit 021ef1220d
4 changed files with 59 additions and 12 deletions

View File

@@ -58,6 +58,7 @@ Docs: https://docs.openclaw.ai
- Plugins/capabilities: cache manifest-derived capability provider plugin IDs per config snapshot so repeated TTS, media, realtime, memory, image, video, and music provider resolution avoids redundant manifest scans. Thanks @shakkernerd.
- Plugins/contracts: resolve runtime manifest-contract plugin owners from one plugin index plus manifest pass instead of rebuilding manifest metadata separately for all owners and enabled owners. Thanks @shakkernerd.
- Plugins/extractors: reuse one manifest registry pass while resolving bundled document and web-content extractor plugins instead of rereading manifests for compatibility and enablement filtering. Thanks @shakkernerd.
- Plugins/providers: reuse one plugin registry snapshot and manifest registry while resolving provider discovery entries instead of rebuilding manifest metadata after provider owner discovery. Thanks @shakkernerd.
- Plugins/registry: resolve lookup-table owner maps for providers, CLI backends, setup providers, command aliases, model catalogs, channel configs, and manifest contracts while preserving setup-only CLI backend ownership. Thanks @shakkernerd.
- Mattermost: keep direct-message replies top-level by suppressing reply roots for DM delivery while preserving channel and group thread roots, and derive inbound chat kind from the trusted channel lookup instead of the websocket event channel type. Carries forward #60115, #55186, #72305, and #72659; refs #59758, #59981, #59791, and #57565. Thanks @vincentkoc, @jwchmodx, and @hnykda.
- Process/Windows: decode command stdout and stderr from raw bytes with console-codepage awareness, while preserving valid UTF-8 output and multibyte characters split across chunks. Fixes #50519. Thanks @iready, @kevinten10, @zhangyongjie1997, @knightplat-blip, @heiqishi666, and @slepybear.

View File

@@ -3,14 +3,19 @@ import type { PluginManifestRecord } from "./manifest-registry.js";
import type { ProviderPlugin } from "./types.js";
const mocks = vi.hoisted(() => ({
loadPluginManifestRegistry: vi.fn(),
loadPluginRegistrySnapshot: vi.fn(),
loadPluginManifestRegistryForInstalledIndex: vi.fn(),
resolveDiscoveredProviderPluginIds: vi.fn(),
resolvePluginProviders: vi.fn(),
loadSource: vi.fn(),
}));
vi.mock("./manifest-registry.js", () => ({
loadPluginManifestRegistry: mocks.loadPluginManifestRegistry,
vi.mock("./plugin-registry.js", () => ({
loadPluginRegistrySnapshot: mocks.loadPluginRegistrySnapshot,
}));
vi.mock("./manifest-registry-installed.js", () => ({
loadPluginManifestRegistryForInstalledIndex: mocks.loadPluginManifestRegistryForInstalledIndex,
}));
vi.mock("./providers.js", () => ({
@@ -77,8 +82,9 @@ function createProvider(params: { id: string; mode: "static" | "catalog" }): Pro
describe("resolvePluginDiscoveryProvidersRuntime", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] });
mocks.resolveDiscoveredProviderPluginIds.mockReturnValue(["deepseek"]);
mocks.loadPluginManifestRegistry.mockReturnValue({
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [createManifestPlugin("deepseek")],
diagnostics: [],
});
@@ -110,7 +116,7 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
"kilocode",
"unused",
]);
mocks.loadPluginManifestRegistry.mockReturnValue({
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
createManifestPlugin("codex"),
createManifestPlugin("deepseek"),
@@ -144,6 +150,35 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
);
});
it("shares one registry snapshot and manifest registry between provider id discovery and entry loading", () => {
const registry = { plugins: [] };
const manifestRegistry = {
plugins: [createManifestPlugin("deepseek")],
diagnostics: [],
};
mocks.loadPluginRegistrySnapshot.mockReturnValue(registry);
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(manifestRegistry);
mocks.loadSource.mockReturnValue(createProvider({ id: "deepseek", mode: "catalog" }));
resolvePluginDiscoveryProvidersRuntime({ config: {}, env: {} as NodeJS.ProcessEnv });
expect(mocks.loadPluginRegistrySnapshot).toHaveBeenCalledOnce();
expect(mocks.loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledWith({
index: registry,
config: {},
workspaceDir: undefined,
env: {},
includeDisabled: true,
});
expect(mocks.loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
expect(mocks.resolveDiscoveredProviderPluginIds).toHaveBeenCalledWith(
expect.objectContaining({
registry,
manifestRegistry,
}),
);
});
it("returns static-only discovery entries for callers that explicitly request them", () => {
const staticProvider = createProvider({ id: "deepseek", mode: "static" });
mocks.loadSource.mockReturnValue(staticProvider);

View File

@@ -1,6 +1,7 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
import type { PluginManifestRecord } from "./manifest-registry.js";
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
import { loadPluginRegistrySnapshot } from "./plugin-registry.js";
import { resolveDiscoveredProviderPluginIds } from "./providers.js";
import { resolvePluginProviders } from "./providers.runtime.js";
import { createPluginSourceLoader } from "./source-loader.js";
@@ -76,13 +77,21 @@ function resolveProviderDiscoveryEntryPlugins(params: {
requireCompleteDiscoveryEntryCoverage?: boolean;
discoveryEntriesOnly?: boolean;
}): ProviderDiscoveryEntryResult {
const pluginIds = resolveDiscoveredProviderPluginIds(params);
const pluginIdSet = new Set(pluginIds);
const pluginRecords = loadPluginManifestRegistryForPluginRegistry({
...params,
pluginIds,
const registry = loadPluginRegistrySnapshot(params);
const manifestRegistry = loadPluginManifestRegistryForInstalledIndex({
index: registry,
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env,
includeDisabled: true,
}).plugins.filter((plugin) => pluginIdSet.has(plugin.id));
});
const pluginIds = resolveDiscoveredProviderPluginIds({
...params,
registry,
manifestRegistry,
});
const pluginIdSet = new Set(pluginIds);
const pluginRecords = manifestRegistry.plugins.filter((plugin) => pluginIdSet.has(plugin.id));
const entryRecords = pluginRecords.filter((plugin) => plugin.providerDiscoverySource);
const entryPluginIds = new Set(entryRecords.map((plugin) => plugin.id));
if (entryRecords.length === 0) {

View File

@@ -233,6 +233,8 @@ export function resolveDiscoveredProviderPluginIds(params: {
config?: PluginLoadOptions["config"];
workspaceDir?: string;
env?: PluginLoadOptions["env"];
registry?: PluginRegistrySnapshot;
manifestRegistry?: PluginManifestRegistry;
onlyPluginIds?: readonly string[];
includeUntrustedWorkspacePlugins?: boolean;
}): string[] {