






















@@ -4,6 +4,7 @@ import type { PluginAutoEnableResult } from "../config/plugin-auto-enable.js";
44import { sortUniqueStrings } from "../shared/string-normalization.js";
55import type { PluginManifestRecord } from "./manifest-registry.js";
66import type { OpenClawPackageManifest } from "./manifest.js";
7+import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js";
78import type { PluginRegistrySnapshot } from "./plugin-registry.js";
89import { createEmptyPluginRegistry } from "./registry-empty.js";
910import type { ProviderPlugin } from "./types.js";
@@ -29,6 +30,7 @@ const loadOpenClawPluginsMock = vi.fn<LoadOpenClawPlugins>();
2930const isPluginRegistryLoadInFlightMock = vi.fn<IsPluginRegistryLoadInFlight>((_) => false);
3031const loadPluginManifestRegistryMock = vi.fn<LoadPluginManifestRegistry>();
3132const loadPluginMetadataSnapshotMock = vi.fn<LoadPluginMetadataSnapshot>();
33+const getCurrentPluginMetadataSnapshotMock = vi.fn();
3234const applyPluginAutoEnableMock = vi.fn<ApplyPluginAutoEnable>();
33353436let resolveOwningPluginIdsForProvider: typeof import("./providers.js").resolveOwningPluginIdsForProvider;
@@ -180,6 +182,45 @@ function createProviderRegistrySnapshotFixture(): PluginRegistrySnapshot {
180182};
181183}
182184185+function createMetadataSnapshotFixture(
186+plugins: PluginManifestRecord[],
187+): Pick<PluginMetadataSnapshot, "owners" | "manifestRegistry" | "byPluginId"> {
188+const ownerMap = (entries: Array<[string, readonly string[]]>) => new Map(entries);
189+return {
190+manifestRegistry: {
191+ plugins,
192+diagnostics: [],
193+},
194+byPluginId: new Map(plugins.map((plugin) => [plugin.id, plugin])),
195+owners: {
196+channels: ownerMap([]),
197+channelConfigs: ownerMap([]),
198+providers: ownerMap(
199+plugins.flatMap((plugin) =>
200+plugin.providers.map((providerId) => [providerId, [plugin.id]] as const),
201+),
202+),
203+modelCatalogProviders: ownerMap(
204+plugins.flatMap((plugin) =>
205+Object.keys(plugin.modelCatalog?.aliases ?? {}).map(
206+(providerId) => [providerId, [plugin.id]] as const,
207+),
208+),
209+),
210+cliBackends: ownerMap(
211+plugins.flatMap((plugin) =>
212+[...plugin.cliBackends, ...(plugin.setup?.cliBackends ?? [])].map(
213+(backendId) => [backendId, [plugin.id]] as const,
214+),
215+),
216+),
217+setupProviders: ownerMap([]),
218+commandAliases: ownerMap([]),
219+contracts: ownerMap([]),
220+},
221+};
222+}
223+183224function normalizeProviderForFixture(value: string): string {
184225return value.trim().toLowerCase();
185226}
@@ -475,6 +516,10 @@ describe("resolvePluginProviders", () => {
475516};
476517},
477518}));
519+vi.doMock("./current-plugin-metadata-snapshot.js", () => ({
520+getCurrentPluginMetadataSnapshot: (...args: unknown[]) =>
521+getCurrentPluginMetadataSnapshotMock(...args),
522+}));
478523vi.doMock("./plugin-registry.js", async () => {
479524const actual =
480525await vi.importActual<typeof import("./plugin-registry.js")>("./plugin-registry.js");
@@ -560,6 +605,96 @@ describe("resolvePluginProviders", () => {
560605expectOwningPluginIds("moonshot-ai", ["moonshot"]);
561606});
562607608+it("uses the current metadata owner maps before loading plugin metadata", () => {
609+const plugins = [
610+createManifestProviderPlugin({
611+id: "openai",
612+providerIds: ["openai", "openai-codex"],
613+}),
614+];
615+getCurrentPluginMetadataSnapshotMock.mockReturnValue(createMetadataSnapshotFixture(plugins));
616+617+expectOwningPluginIds("openai-codex", ["openai"]);
618+619+expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled();
620+expect(getCurrentPluginMetadataSnapshotMock).toHaveBeenCalledWith({
621+config: undefined,
622+env: undefined,
623+allowWorkspaceScopedSnapshot: true,
624+});
625+});
626+627+it("uses current metadata owner maps for cli backend provider refs", () => {
628+const plugins = [
629+createManifestProviderPlugin({
630+id: "anthropic",
631+providerIds: [],
632+cliBackends: ["claude-cli"],
633+}),
634+];
635+getCurrentPluginMetadataSnapshotMock.mockReturnValue(createMetadataSnapshotFixture(plugins));
636+637+expect(resolveOwningPluginIdsForProviderRef({ provider: "claude-cli" })).toEqual(["anthropic"]);
638+639+expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled();
640+});
641+642+it("keeps normalized case-variant owners from current metadata maps", () => {
643+const plugins = [
644+createManifestProviderPlugin({
645+id: "exact-owner",
646+providerIds: ["codex-cli"],
647+cliBackends: ["codex-cli"],
648+}),
649+createManifestProviderPlugin({
650+id: "case-owner",
651+providerIds: ["CODEX-CLI"],
652+cliBackends: ["CODEX-CLI"],
653+}),
654+];
655+getCurrentPluginMetadataSnapshotMock.mockReturnValue(createMetadataSnapshotFixture(plugins));
656+657+expect(resolveOwningPluginIdsForProvider({ provider: "codex-cli" })).toEqual([
658+"case-owner",
659+"exact-owner",
660+]);
661+expect(resolveOwningPluginIdsForProviderRef({ provider: "codex-cli" })).toEqual([
662+"case-owner",
663+"exact-owner",
664+]);
665+});
666+667+it("keeps explicit manifest registries ahead of current metadata owner maps", () => {
668+getCurrentPluginMetadataSnapshotMock.mockReturnValue(
669+createMetadataSnapshotFixture([
670+createManifestProviderPlugin({
671+id: "stale-owner",
672+providerIds: ["dynamic-provider"],
673+cliBackends: ["dynamic-cli"],
674+}),
675+]),
676+);
677+const manifestRegistry = {
678+diagnostics: [],
679+plugins: [
680+createManifestProviderPlugin({
681+id: "fresh-owner",
682+providerIds: ["dynamic-provider"],
683+cliBackends: ["dynamic-cli"],
684+}),
685+],
686+};
687+688+expect(
689+resolveOwningPluginIdsForProvider({ provider: "dynamic-provider", manifestRegistry }),
690+).toEqual(["fresh-owner"]);
691+expect(
692+resolveOwningPluginIdsForProviderRef({ provider: "dynamic-cli", manifestRegistry }),
693+).toEqual(["fresh-owner"]);
694+695+expect(getCurrentPluginMetadataSnapshotMock).not.toHaveBeenCalled();
696+});
697+563698it("reflects provider ownership manifest changes on the next lookup", () => {
564699setManifestPlugins([
565700createManifestProviderPlugin({
@@ -588,6 +723,8 @@ describe("resolvePluginProviders", () => {
588723isPluginRegistryLoadInFlightMock.mockReset();
589724isPluginRegistryLoadInFlightMock.mockReturnValue(false);
590725loadPluginMetadataSnapshotMock.mockReset();
726+getCurrentPluginMetadataSnapshotMock.mockReset();
727+getCurrentPluginMetadataSnapshotMock.mockReturnValue(undefined);
591728const provider: ProviderPlugin = {
592729id: "demo-provider",
593730label: "Demo Provider",
@@ -665,6 +802,28 @@ describe("resolvePluginProviders", () => {
665802]);
666803});
667804805+it("does not answer explicit registry lookups from current metadata snapshots", () => {
806+setOwningProviderManifestPlugins();
807+getCurrentPluginMetadataSnapshotMock.mockReturnValue(
808+createMetadataSnapshotFixture([
809+createManifestProviderPlugin({
810+id: "stale-owner",
811+providerIds: ["stale-provider"],
812+}),
813+]),
814+);
815+816+expect(
817+resolveEnabledProviderPluginIds({
818+config: {},
819+env: {} as NodeJS.ProcessEnv,
820+registry: createProviderRegistrySnapshotFixture(),
821+}),
822+).toEqual([]);
823+824+expect(getCurrentPluginMetadataSnapshotMock).not.toHaveBeenCalled();
825+});
826+668827it("loads catalog augment hooks only for declarative runtime catalog manifests", () => {
669828setManifestPlugins([
670829createManifestProviderPlugin({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。