

























@@ -931,4 +931,206 @@ describe("setup-registry module loader", () => {
931931expect(loadSetupModule).toHaveBeenCalledTimes(1);
932932expect(registerSetup).toHaveBeenCalledTimes(7);
933933});
934-});
934+935+describe("result cache (ambient process.env)", () => {
936+function mockOpenAiProviderPlugin(): void {
937+mocks.loadPluginManifestRegistry.mockReturnValue({
938+plugins: [
939+{
940+id: "openai",
941+origin: "bundled",
942+rootDir: makeTempDir(),
943+setup: { providers: [{ id: "openai" }] },
944+},
945+],
946+diagnostics: [],
947+});
948+}
949+950+it("memoizes no-config resolutions without rescanning manifests", () => {
951+mockOpenAiProviderPlugin();
952+mocks.loadPluginManifestRegistry.mockClear();
953+const first = resolvePluginSetupRegistry();
954+const second = resolvePluginSetupRegistry();
955+expect(second).toEqual(first);
956+expect(second).not.toBe(first);
957+expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(1);
958+});
959+960+it("recomputes after clearPluginSetupRegistryCache (reset contract)", () => {
961+mockOpenAiProviderPlugin();
962+mocks.loadPluginManifestRegistry.mockClear();
963+const first = resolvePluginSetupRegistry();
964+expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(1);
965+clearPluginSetupRegistryCache();
966+// A hit also returns a fresh clone; only the loader call count proves invalidation.
967+const second = resolvePluginSetupRegistry();
968+expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(2);
969+expect(second).toEqual(first);
970+});
971+972+it("recomputes after clearPluginMetadataLifecycleCaches (install/reload/doctor)", async () => {
973+const { clearPluginMetadataLifecycleCaches } = await import("./plugin-metadata-lifecycle.js");
974+mockOpenAiProviderPlugin();
975+mocks.loadPluginManifestRegistry.mockClear();
976+const first = resolvePluginSetupRegistry();
977+expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(1);
978+clearPluginMetadataLifecycleCaches();
979+const second = resolvePluginSetupRegistry();
980+expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(2);
981+expect(second).toEqual(first);
982+});
983+984+it("keys distinct plugin-id scopes without collision", () => {
985+mockOpenAiProviderPlugin();
986+const all = resolvePluginSetupRegistry();
987+const scoped = resolvePluginSetupRegistry({ pluginIds: ["__no_such_plugin__"] });
988+expect(scoped).not.toBe(all);
989+expect(scoped.providers).toStrictEqual([]);
990+const scopedAgain = resolvePluginSetupRegistry({ pluginIds: ["__no_such_plugin__"] });
991+expect(scopedAgain).toEqual(scoped);
992+expect(scopedAgain).not.toBe(scoped);
993+});
994+995+it("does not let returned entries mutate cached setup results", () => {
996+const pluginRoot = makeTempDir();
997+writeSetupApiStub(pluginRoot);
998+mocks.loadPluginManifestRegistry.mockReturnValue({
999+plugins: [
1000+{
1001+id: "openai",
1002+origin: "bundled",
1003+rootDir: pluginRoot,
1004+setup: {
1005+providers: [{ id: "openai" }],
1006+cliBackends: ["codex-cli"],
1007+requiresRuntime: true,
1008+},
1009+},
1010+],
1011+diagnostics: [],
1012+});
1013+mocks.createJiti.mockImplementation(() => {
1014+return () => ({
1015+default: {
1016+register(api: {
1017+registerProvider: (provider: {
1018+id: string;
1019+label: string;
1020+aliases: string[];
1021+auth: [];
1022+}) => void;
1023+registerCliBackend: (backend: {
1024+id: string;
1025+config: { command: string; args: string[] };
1026+}) => void;
1027+}) {
1028+api.registerProvider({
1029+id: "openai",
1030+label: "OpenAI",
1031+aliases: ["openai"],
1032+auth: [],
1033+});
1034+api.registerCliBackend({
1035+id: "codex-cli",
1036+config: { command: "codex", args: ["run"] },
1037+});
1038+},
1039+},
1040+});
1041+});
1042+1043+const first = resolvePluginSetupRegistry();
1044+const firstProvider = first.providers[0]?.provider;
1045+const firstBackend = first.cliBackends[0]?.backend;
1046+if (!firstProvider || !firstBackend) {
1047+throw new Error("Expected setup provider and CLI backend");
1048+}
1049+firstProvider.label = "Mutated";
1050+firstProvider.aliases?.push("mutated");
1051+const firstBackendConfig = firstBackend.config as { command: string; args: string[] };
1052+firstBackendConfig.command = "mutated";
1053+firstBackendConfig.args.push("mutated");
1054+1055+const second = resolvePluginSetupRegistry();
1056+expect(second.providers[0]?.provider).toMatchObject({
1057+id: "openai",
1058+label: "OpenAI",
1059+aliases: ["openai"],
1060+});
1061+expect(second.cliBackends[0]?.backend.config).toMatchObject({
1062+command: "codex",
1063+args: ["run"],
1064+});
1065+1066+second.providers[0]?.provider.aliases?.push("mutated-again");
1067+const secondBackendConfig = second.cliBackends[0]?.backend.config as {
1068+command: string;
1069+args: string[];
1070+};
1071+secondBackendConfig.args.push("mutated-again");
1072+1073+const third = resolvePluginSetupRegistry();
1074+expect(third.providers[0]?.provider.aliases).toEqual(["openai"]);
1075+expect(third.cliBackends[0]?.backend.config).toMatchObject({
1076+command: "codex",
1077+args: ["run"],
1078+});
1079+expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(1);
1080+});
1081+1082+it("keys ambient entries by discovery environment", () => {
1083+const firstRoot = makeTempDir();
1084+const secondRoot = makeTempDir();
1085+writeSetupApiStub(firstRoot);
1086+writeSetupApiStub(secondRoot);
1087+mocks.loadPluginManifestRegistry.mockImplementation(
1088+(params?: { env?: NodeJS.ProcessEnv }) => {
1089+const id = params?.env?.OPENCLAW_BUNDLED_PLUGINS_DIR === secondRoot ? "second" : "first";
1090+return {
1091+plugins: [
1092+{
1093+ id,
1094+origin: "bundled",
1095+rootDir: id === "second" ? secondRoot : firstRoot,
1096+setup: { providers: [{ id }] },
1097+},
1098+],
1099+diagnostics: [],
1100+};
1101+},
1102+);
1103+mocks.createJiti.mockImplementation((modulePath: string) => {
1104+const id = modulePath.includes(secondRoot) ? "second" : "first";
1105+return () => ({
1106+default: {
1107+register(api: {
1108+registerProvider: (provider: { id: string; label: string; auth: [] }) => void;
1109+}) {
1110+api.registerProvider({ id, label: id, auth: [] });
1111+},
1112+},
1113+});
1114+});
1115+const previousBundledDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
1116+1117+try {
1118+process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = firstRoot;
1119+expect(resolvePluginSetupRegistry().providers.map((entry) => entry.provider.id)).toEqual([
1120+"first",
1121+]);
1122+process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = secondRoot;
1123+expect(resolvePluginSetupRegistry().providers.map((entry) => entry.provider.id)).toEqual([
1124+"second",
1125+]);
1126+} finally {
1127+if (previousBundledDir === undefined) {
1128+delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
1129+} else {
1130+process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = previousBundledDir;
1131+}
1132+}
1133+expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(2);
1134+});
1135+});
1136+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。