refactor: reuse plugin metadata snapshots

This commit is contained in:
Peter Steinberger
2026-05-02 07:13:11 +01:00
parent f9cdf2f552
commit 71da5af164
13 changed files with 312 additions and 118 deletions

View File

@@ -202,6 +202,77 @@ describe("model-pricing-cache", () => {
expect(fetchImpl).not.toHaveBeenCalled();
});
it("uses a provided metadata registry view without rebuilding manifest metadata", async () => {
const manifestRegistry = {
diagnostics: [],
plugins: [
createManifestRecord({
id: "search-plugin",
contracts: { webSearchProviders: ["search-plugin"] },
}),
],
};
const config = {
plugins: {
entries: {
"search-plugin": {
config: {
webSearch: {
model: "local-search/search-model",
},
},
},
},
},
models: {
providers: {
"local-search": {
baseUrl: "http://127.0.0.1:43210/v1",
api: "openai-completions",
models: [
{
id: "search-model",
cost: { input: 0.2, output: 0.4 },
},
],
},
},
},
} as unknown as OpenClawConfig;
const fetchImpl = vi.fn<typeof fetch>();
await refreshGatewayModelPricingCache({
config,
fetchImpl,
pluginMetadataSnapshot: {
index: {
plugins: [
{
pluginId: "search-plugin",
origin: "global",
enabled: true,
enabledByDefault: true,
},
],
} as never,
manifestRegistry,
},
});
expect(
pluginManifestRegistryMocks.loadPluginManifestRegistryForInstalledIndex,
).not.toHaveBeenCalled();
expect(fetchImpl).not.toHaveBeenCalled();
expect(
getCachedGatewayModelPricing({ provider: "local-search", model: "search-model" }),
).toEqual({
input: 0.2,
output: 0.4,
cacheRead: 0,
cacheWrite: 0,
});
});
it("does not load plugin manifests for pricing when plugins are globally disabled", async () => {
const config = {
plugins: {

View File

@@ -13,18 +13,15 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { planManifestModelCatalogRows, type ModelCatalogCost } from "../model-catalog/index.js";
import { isInstalledPluginEnabled } from "../plugins/installed-plugin-index.js";
import { loadPluginManifestRegistryForInstalledIndex } from "../plugins/manifest-registry-installed.js";
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
import type {
PluginManifestModelPricingModelIdTransform,
PluginManifestModelPricingProvider,
PluginManifestModelPricingSource,
} from "../plugins/manifest.js";
import type { PluginLookUpTable } from "../plugins/plugin-lookup-table.js";
import {
loadPluginRegistrySnapshot,
type PluginRegistrySnapshot,
} from "../plugins/plugin-registry.js";
import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
import type { PluginMetadataRegistryView } from "../plugins/plugin-metadata-snapshot.types.js";
import type { PluginRegistrySnapshot } from "../plugins/plugin-registry.js";
import { normalizeOptionalString, resolvePrimaryStringValue } from "../shared/string-coerce.js";
import {
clearGatewayModelPricingCacheState,
@@ -400,15 +397,19 @@ function filterActiveManifestRegistry(params: {
function resolveModelPricingManifestMetadata(params: {
config: OpenClawConfig;
pluginLookUpTable?: Pick<PluginLookUpTable, "index" | "manifestRegistry">;
env?: NodeJS.ProcessEnv;
workspaceDir?: string;
pluginMetadataSnapshot?: PluginMetadataRegistryView;
pluginLookUpTable?: PluginMetadataRegistryView;
manifestRegistry?: PluginManifestRegistry;
}): ModelPricingManifestMetadata {
if (params.pluginLookUpTable) {
const metadataSnapshot = params.pluginMetadataSnapshot ?? params.pluginLookUpTable;
if (metadataSnapshot) {
return {
allRegistry: params.pluginLookUpTable.manifestRegistry,
allRegistry: metadataSnapshot.manifestRegistry,
activeRegistry: filterActiveManifestRegistry({
registry: params.pluginLookUpTable.manifestRegistry,
index: params.pluginLookUpTable.index,
registry: metadataSnapshot.manifestRegistry,
index: metadataSnapshot.index,
config: params.config,
}),
};
@@ -426,17 +427,16 @@ function resolveModelPricingManifestMetadata(params: {
activeRegistry: emptyRegistry,
};
}
const index = loadPluginRegistrySnapshot({ config: params.config });
const allRegistry = loadPluginManifestRegistryForInstalledIndex({
index,
const snapshot = loadPluginMetadataSnapshot({
config: params.config,
includeDisabled: true,
env: params.env ?? process.env,
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
});
return {
allRegistry,
allRegistry: snapshot.manifestRegistry,
activeRegistry: filterActiveManifestRegistry({
registry: allRegistry,
index,
registry: snapshot.manifestRegistry,
index: snapshot.index,
config: params.config,
}),
};
@@ -1102,8 +1102,11 @@ function collectSeededPricing(params: {
export async function refreshGatewayModelPricingCache(params: {
config: OpenClawConfig;
env?: NodeJS.ProcessEnv;
fetchImpl?: typeof fetch;
pluginLookUpTable?: Pick<PluginLookUpTable, "index" | "manifestRegistry">;
workspaceDir?: string;
pluginMetadataSnapshot?: PluginMetadataRegistryView;
pluginLookUpTable?: PluginMetadataRegistryView;
manifestRegistry?: PluginManifestRegistry;
}): Promise<void> {
if (!isGatewayModelPricingEnabled(params.config)) {
@@ -1117,6 +1120,9 @@ export async function refreshGatewayModelPricingCache(params: {
inFlightRefresh = (async () => {
const manifestMetadata = resolveModelPricingManifestMetadata({
config: params.config,
env: params.env,
workspaceDir: params.workspaceDir,
pluginMetadataSnapshot: params.pluginMetadataSnapshot,
pluginLookUpTable: params.pluginLookUpTable,
manifestRegistry: params.manifestRegistry,
});
@@ -1251,8 +1257,11 @@ export async function refreshGatewayModelPricingCache(params: {
export function startGatewayModelPricingRefresh(params: {
config: OpenClawConfig;
env?: NodeJS.ProcessEnv;
fetchImpl?: typeof fetch;
pluginLookUpTable?: Pick<PluginLookUpTable, "index" | "manifestRegistry">;
workspaceDir?: string;
pluginMetadataSnapshot?: PluginMetadataRegistryView;
pluginLookUpTable?: PluginMetadataRegistryView;
manifestRegistry?: PluginManifestRegistry;
}): () => void {
if (!isGatewayModelPricingEnabled(params.config)) {

View File

@@ -1,7 +1,7 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { isVitestRuntimeEnv } from "../infra/env.js";
import { startHeartbeatRunner, type HeartbeatRunner } from "../infra/heartbeat-runner.js";
import type { PluginLookUpTable } from "../plugins/plugin-lookup-table.js";
import type { PluginMetadataRegistryView } from "../plugins/plugin-metadata-snapshot.types.js";
import type { ChannelHealthMonitor } from "./channel-health-monitor.js";
import { startChannelHealthMonitor } from "./channel-health-monitor.js";
import { isGatewayModelPricingEnabled } from "./model-pricing-config.js";
@@ -91,7 +91,7 @@ function recoverPendingSessionDeliveries(params: {
function startGatewayModelPricingRefreshOnDemand(params: {
config: OpenClawConfig;
pluginLookUpTable?: Pick<PluginLookUpTable, "index" | "manifestRegistry">;
pluginLookUpTable?: PluginMetadataRegistryView;
log: GatewayRuntimeServiceLogger;
}): () => void {
if (!isGatewayModelPricingEnabled(params.config)) {
@@ -125,7 +125,7 @@ export function startGatewayRuntimeServices(params: {
cfgAtStart: OpenClawConfig;
channelManager: GatewayChannelManager;
log: GatewayRuntimeServiceLogger;
pluginLookUpTable?: Pick<PluginLookUpTable, "index" | "manifestRegistry">;
pluginLookUpTable?: PluginMetadataRegistryView;
}): {
heartbeatRunner: HeartbeatRunner;
channelHealthMonitor: ChannelHealthMonitor | null;

View File

@@ -0,0 +1,66 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
getCurrentPluginMetadataSnapshot: vi.fn(),
loadPluginMetadataSnapshot: vi.fn(),
}));
vi.mock("./current-plugin-metadata-snapshot.js", () => ({
getCurrentPluginMetadataSnapshot: mocks.getCurrentPluginMetadataSnapshot,
}));
vi.mock("./plugin-metadata-snapshot.js", () => ({
loadPluginMetadataSnapshot: mocks.loadPluginMetadataSnapshot,
}));
import { loadManifestContractSnapshot } from "./manifest-contract-eligibility.js";
describe("loadManifestContractSnapshot", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.getCurrentPluginMetadataSnapshot.mockReturnValue(undefined);
mocks.loadPluginMetadataSnapshot.mockReturnValue({
index: { plugins: [] },
plugins: [],
});
});
it("checks the current metadata snapshot with env and workspace scope", () => {
const env = { HOME: "/home/snapshot" } as NodeJS.ProcessEnv;
const current = {
index: { plugins: [] },
plugins: [],
};
mocks.getCurrentPluginMetadataSnapshot.mockReturnValue(current);
expect(loadManifestContractSnapshot({ config: {}, workspaceDir: "/workspace", env })).toBe(
current,
);
expect(mocks.getCurrentPluginMetadataSnapshot).toHaveBeenCalledWith({
config: {},
env,
workspaceDir: "/workspace",
});
expect(mocks.loadPluginMetadataSnapshot).not.toHaveBeenCalled();
});
it("falls back to the shared metadata snapshot loader", () => {
const env = { HOME: "/home/fallback" } as NodeJS.ProcessEnv;
const snapshot = {
index: { plugins: [{ pluginId: "demo" }] },
plugins: [{ id: "demo" }],
};
mocks.loadPluginMetadataSnapshot.mockReturnValue(snapshot);
expect(loadManifestContractSnapshot({ config: {}, env })).toEqual({
index: snapshot.index,
plugins: snapshot.plugins,
});
expect(mocks.loadPluginMetadataSnapshot).toHaveBeenCalledWith({
config: {},
env,
});
});
});

View File

@@ -1,10 +1,12 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot.js";
import { isInstalledPluginEnabled } from "./installed-plugin-index.js";
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
import type { PluginManifestContractListKey, PluginManifestRecord } from "./manifest-registry.js";
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js";
import { loadPluginRegistrySnapshot } from "./plugin-registry.js";
import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
import type {
PluginMetadataManifestView,
PluginMetadataSnapshot,
} from "./plugin-metadata-snapshot.types.js";
export function isManifestPluginAvailableForControlPlane(params: {
snapshot: Pick<PluginMetadataSnapshot, "index">;
@@ -65,28 +67,23 @@ export function loadManifestContractSnapshot(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): Pick<PluginMetadataSnapshot, "index" | "plugins"> {
}): PluginMetadataManifestView {
const env = params.env ?? process.env;
const current = getCurrentPluginMetadataSnapshot({
config: params.config,
env,
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
});
if (current) {
return current;
}
const env = params.env ?? process.env;
const index = loadPluginRegistrySnapshot({
config: params.config,
const snapshot = loadPluginMetadataSnapshot({
config: params.config ?? {},
env,
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
});
return {
index,
plugins: loadPluginManifestRegistryForInstalledIndex({
index,
config: params.config,
env,
includeDisabled: true,
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
}).plugins,
index: snapshot.index,
plugins: snapshot.plugins,
};
}

View File

@@ -17,6 +17,8 @@ import { createPluginRegistryIdNormalizer } from "./plugin-registry-id-normalize
import { loadPluginRegistrySnapshotWithMetadata } from "./plugin-registry-snapshot.js";
export type {
LoadPluginMetadataSnapshotParams,
PluginMetadataManifestView,
PluginMetadataRegistryView,
PluginMetadataSnapshot,
PluginMetadataSnapshotMetrics,
PluginMetadataSnapshotOwnerMaps,
@@ -146,6 +148,12 @@ function buildPluginMetadataOwnerMaps(
};
}
export function listPluginOriginsFromMetadataSnapshot(
snapshot: Pick<PluginMetadataSnapshot, "plugins">,
): ReadonlyMap<string, PluginManifestRecord["origin"]> {
return new Map(snapshot.plugins.map((record) => [record.id, record.origin]));
}
export function loadPluginMetadataSnapshot(
params: LoadPluginMetadataSnapshotParams,
): PluginMetadataSnapshot {

View File

@@ -48,6 +48,10 @@ export type PluginMetadataSnapshot = {
metrics: PluginMetadataSnapshotMetrics;
};
export type PluginMetadataRegistryView = Pick<PluginMetadataSnapshot, "index" | "manifestRegistry">;
export type PluginMetadataManifestView = Pick<PluginMetadataSnapshot, "index" | "plugins">;
export type LoadPluginMetadataSnapshotParams = {
config: OpenClawConfig;
workspaceDir?: string;

View File

@@ -3,20 +3,19 @@ import type { PluginManifestRecord } from "./manifest-registry.js";
import type { ProviderPlugin } from "./types.js";
const mocks = vi.hoisted(() => ({
loadPluginRegistrySnapshot: vi.fn(),
loadPluginManifestRegistryForInstalledIndex: vi.fn(),
loadPluginMetadataSnapshot: vi.fn(),
resolveDiscoveredProviderPluginIds: vi.fn(),
resolvePluginProviders: vi.fn(),
loadSource: vi.fn(),
}));
vi.mock("./plugin-registry.js", () => ({
loadPluginRegistrySnapshot: mocks.loadPluginRegistrySnapshot,
}));
vi.mock("./manifest-registry-installed.js", () => ({
loadPluginManifestRegistryForInstalledIndex: mocks.loadPluginManifestRegistryForInstalledIndex,
}));
vi.mock("./plugin-metadata-snapshot.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./plugin-metadata-snapshot.js")>();
return {
...actual,
loadPluginMetadataSnapshot: mocks.loadPluginMetadataSnapshot,
};
});
vi.mock("./providers.js", () => ({
resolveDiscoveredProviderPluginIds: mocks.resolveDiscoveredProviderPluginIds,
@@ -82,11 +81,13 @@ function createProvider(params: { id: string; mode: "static" | "catalog" }): Pro
describe("resolvePluginDiscoveryProvidersRuntime", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] });
mocks.resolveDiscoveredProviderPluginIds.mockReturnValue(["deepseek"]);
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [createManifestPlugin("deepseek")],
diagnostics: [],
mocks.loadPluginMetadataSnapshot.mockReturnValue({
index: { plugins: [] },
manifestRegistry: {
plugins: [createManifestPlugin("deepseek")],
diagnostics: [],
},
});
});
@@ -116,20 +117,23 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
"kilocode",
"unused",
]);
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [
createManifestPlugin("codex"),
createManifestPlugin("deepseek"),
createManifestPluginWithoutDiscovery({
id: "kilocode",
providerAuthEnvVars: { kilocode: ["KILOCODE_API_KEY"] },
}),
createManifestPluginWithoutDiscovery({
id: "unused",
providerAuthEnvVars: { unused: ["UNUSED_API_KEY"] },
}),
],
diagnostics: [],
mocks.loadPluginMetadataSnapshot.mockReturnValue({
index: { plugins: [] },
manifestRegistry: {
plugins: [
createManifestPlugin("codex"),
createManifestPlugin("deepseek"),
createManifestPluginWithoutDiscovery({
id: "kilocode",
providerAuthEnvVars: { kilocode: ["KILOCODE_API_KEY"] },
}),
createManifestPluginWithoutDiscovery({
id: "unused",
providerAuthEnvVars: { unused: ["UNUSED_API_KEY"] },
}),
],
diagnostics: [],
},
});
mocks.loadSource.mockImplementation((modulePath: string) =>
modulePath.includes("/codex/")
@@ -150,27 +154,25 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
);
});
it("shares one registry snapshot and manifest registry between provider id discovery and entry loading", () => {
it("shares one metadata snapshot 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.loadPluginMetadataSnapshot.mockReturnValue({
index: registry,
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,
expect(mocks.loadPluginMetadataSnapshot).toHaveBeenCalledWith({
config: {},
workspaceDir: undefined,
env: {},
includeDisabled: true,
});
expect(mocks.loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
expect(mocks.loadPluginMetadataSnapshot).toHaveBeenCalledOnce();
expect(mocks.resolveDiscoveredProviderPluginIds).toHaveBeenCalledWith(
expect.objectContaining({
registry,
@@ -203,8 +205,7 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
}),
]);
expect(mocks.loadPluginRegistrySnapshot).not.toHaveBeenCalled();
expect(mocks.loadPluginManifestRegistryForInstalledIndex).not.toHaveBeenCalled();
expect(mocks.loadPluginMetadataSnapshot).not.toHaveBeenCalled();
expect(mocks.resolveDiscoveredProviderPluginIds).toHaveBeenCalledWith(
expect.objectContaining({
registry,
@@ -228,9 +229,12 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
});
it("does not fall back to full plugin loading when discovery entries are requested only", () => {
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
plugins: [createManifestPluginWithoutDiscovery({ id: "deepseek" })],
diagnostics: [],
mocks.loadPluginMetadataSnapshot.mockReturnValue({
index: { plugins: [] },
manifestRegistry: {
plugins: [createManifestPluginWithoutDiscovery({ id: "deepseek" })],
diagnostics: [],
},
});
expect(resolvePluginDiscoveryProvidersRuntime({ discoveryEntriesOnly: true })).toEqual([]);

View File

@@ -1,8 +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 type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
import { loadPluginRegistrySnapshot } from "./plugin-registry.js";
import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
import type { PluginMetadataRegistryView } from "./plugin-metadata-snapshot.types.js";
import { resolveDiscoveredProviderPluginIds } from "./providers.js";
import { resolvePluginProviders } from "./providers.runtime.js";
import { createPluginSourceLoader } from "./source-loader.js";
@@ -77,18 +76,17 @@ function resolveProviderDiscoveryEntryPlugins(params: {
includeUntrustedWorkspacePlugins?: boolean;
requireCompleteDiscoveryEntryCoverage?: boolean;
discoveryEntriesOnly?: boolean;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry">;
pluginMetadataSnapshot?: PluginMetadataRegistryView;
}): ProviderDiscoveryEntryResult {
const registry = params.pluginMetadataSnapshot?.index ?? loadPluginRegistrySnapshot(params);
const manifestRegistry =
params.pluginMetadataSnapshot?.manifestRegistry ??
loadPluginManifestRegistryForInstalledIndex({
index: registry,
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env,
includeDisabled: true,
const metadataSnapshot =
params.pluginMetadataSnapshot ??
loadPluginMetadataSnapshot({
config: params.config ?? {},
env: params.env ?? process.env,
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
});
const registry = metadataSnapshot.index;
const manifestRegistry = metadataSnapshot.manifestRegistry;
const pluginIds = resolveDiscoveredProviderPluginIds({
...params,
registry,
@@ -148,10 +146,10 @@ export function resolvePluginDiscoveryProvidersRuntime(params: {
includeUntrustedWorkspacePlugins?: boolean;
requireCompleteDiscoveryEntryCoverage?: boolean;
discoveryEntriesOnly?: boolean;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry">;
pluginMetadataSnapshot?: PluginMetadataRegistryView;
}): ProviderPlugin[] {
const env = params.env ?? process.env;
const entryResult = resolveProviderDiscoveryEntryPlugins(params);
const entryResult = resolveProviderDiscoveryEntryPlugins({ ...params, env });
if (params.discoveryEntriesOnly === true) {
return entryResult.providers;
}

View File

@@ -1,7 +1,7 @@
import { normalizeProviderId } from "../agents/model-selection.js";
import type { ModelProviderConfig } from "../config/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
import type { PluginMetadataRegistryView } from "./plugin-metadata-snapshot.types.js";
import {
listPluginContributionIds,
loadPluginRegistrySnapshot,
@@ -43,7 +43,7 @@ export type ResolveRuntimePluginDiscoveryProvidersParams = {
includeUntrustedWorkspacePlugins?: boolean;
requireCompleteDiscoveryEntryCoverage?: boolean;
discoveryEntriesOnly?: boolean;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry">;
pluginMetadataSnapshot?: PluginMetadataRegistryView;
};
export type ResolveInstalledPluginProviderContributionIdsParams = LoadPluginRegistryParams & {

View File

@@ -1,2 +1,4 @@
export { loadPluginManifestRegistryForInstalledIndex } from "../plugins/manifest-registry-installed.js";
export { loadPluginRegistrySnapshot } from "../plugins/plugin-registry.js";
export {
listPluginOriginsFromMetadataSnapshot,
loadPluginMetadataSnapshot,
} from "../plugins/plugin-metadata-snapshot.js";

View File

@@ -2,25 +2,32 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import { asConfig, setupSecretsRuntimeSnapshotTestHooks } from "./runtime.test-support.ts";
const manifestMocks = vi.hoisted(() => ({
loadPluginManifestRegistryForInstalledIndex: vi.fn(),
loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })),
listPluginOriginsFromMetadataSnapshot: vi.fn(
(snapshot: { plugins: Array<{ id: string; origin: string }> }) =>
new Map(snapshot.plugins.map((record) => [record.id, record.origin])),
),
loadPluginMetadataSnapshot: vi.fn<() => { plugins: Array<{ id: string; origin: string }> }>(
() => ({
plugins: [],
}),
),
}));
vi.mock("./runtime-manifest.runtime.js", () => ({
loadPluginManifestRegistryForInstalledIndex:
manifestMocks.loadPluginManifestRegistryForInstalledIndex,
loadPluginRegistrySnapshot: manifestMocks.loadPluginRegistrySnapshot,
listPluginOriginsFromMetadataSnapshot: manifestMocks.listPluginOriginsFromMetadataSnapshot,
loadPluginMetadataSnapshot: manifestMocks.loadPluginMetadataSnapshot,
}));
const { prepareSecretsRuntimeSnapshot } = setupSecretsRuntimeSnapshotTestHooks();
describe("prepareSecretsRuntimeSnapshot loadable plugin origins", () => {
afterEach(() => {
manifestMocks.loadPluginManifestRegistryForInstalledIndex.mockReset();
manifestMocks.loadPluginRegistrySnapshot.mockReset();
manifestMocks.listPluginOriginsFromMetadataSnapshot.mockClear();
manifestMocks.loadPluginMetadataSnapshot.mockReset();
manifestMocks.loadPluginMetadataSnapshot.mockReturnValue({ plugins: [] });
});
it("skips manifest registry loading when plugin entries are absent", async () => {
it("skips metadata snapshot loading when plugin entries are absent", async () => {
await prepareSecretsRuntimeSnapshot({
config: asConfig({
models: {
@@ -36,7 +43,42 @@ describe("prepareSecretsRuntimeSnapshot loadable plugin origins", () => {
includeAuthStoreRefs: false,
});
expect(manifestMocks.loadPluginManifestRegistryForInstalledIndex).not.toHaveBeenCalled();
expect(manifestMocks.loadPluginRegistrySnapshot).not.toHaveBeenCalled();
expect(manifestMocks.loadPluginMetadataSnapshot).not.toHaveBeenCalled();
expect(manifestMocks.listPluginOriginsFromMetadataSnapshot).not.toHaveBeenCalled();
});
it("derives loadable plugin origins from the shared metadata snapshot", async () => {
const snapshot = {
plugins: [{ id: "demo", origin: "workspace" }],
};
manifestMocks.loadPluginMetadataSnapshot.mockReturnValue(snapshot);
await prepareSecretsRuntimeSnapshot({
config: asConfig({
plugins: {
entries: {
demo: {
config: {
apiKey: { source: "env", provider: "default", id: "DEMO_API_KEY" },
},
},
},
},
}),
env: { HOME: "/home/demo", DEMO_API_KEY: "sk-demo" },
includeAuthStoreRefs: false,
});
expect(manifestMocks.loadPluginMetadataSnapshot).toHaveBeenCalledWith({
config: expect.objectContaining({
plugins: expect.any(Object),
}),
workspaceDir: expect.any(String),
env: expect.objectContaining({
HOME: "/home/demo",
DEMO_API_KEY: "sk-demo",
}),
});
expect(manifestMocks.listPluginOriginsFromMetadataSnapshot).toHaveBeenCalledWith(snapshot);
});
});

View File

@@ -140,21 +140,14 @@ async function resolveLoadablePluginOrigins(params: {
params.config,
resolveDefaultAgentId(params.config),
);
const { loadPluginManifestRegistryForInstalledIndex, loadPluginRegistrySnapshot } =
const { listPluginOriginsFromMetadataSnapshot, loadPluginMetadataSnapshot } =
await loadRuntimeManifestHelpers();
const index = loadPluginRegistrySnapshot({
const snapshot = loadPluginMetadataSnapshot({
config: params.config,
workspaceDir,
env: params.env,
});
const manifestRegistry = loadPluginManifestRegistryForInstalledIndex({
index,
config: params.config,
workspaceDir,
env: params.env,
includeDisabled: true,
});
return new Map(manifestRegistry.plugins.map((record) => [record.id, record.origin]));
return listPluginOriginsFromMetadataSnapshot(snapshot);
}
function mergeSecretsRuntimeEnv(