






















@@ -0,0 +1,186 @@
1+import fs from "node:fs";
2+import os from "node:os";
3+import path from "node:path";
4+import { afterEach, describe, expect, it } from "vitest";
5+import type { OpenClawConfig } from "../config/types.openclaw.js";
6+import { clearPluginDiscoveryCache } from "../plugins/discovery.js";
7+import { clearPluginManifestRegistryCache } from "../plugins/manifest-registry.js";
8+import { refreshPluginRegistry } from "../plugins/plugin-registry.js";
9+import { buildAuthChoiceOptions, formatAuthChoiceChoicesForCli } from "./auth-choice-options.js";
10+import { listManifestInstalledChannelIds } from "./channel-setup/discovery.js";
11+import { resolveProviderCatalogPluginIdsForFilter } from "./models/list.provider-catalog.js";
12+13+const tempDirs: string[] = [];
14+15+function makeTempDir() {
16+const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-command-cold-imports-"));
17+tempDirs.push(dir);
18+return dir;
19+}
20+21+function hermeticEnv(
22+homeDir: string,
23+options: { disablePersistedRegistry?: boolean } = {},
24+): NodeJS.ProcessEnv {
25+return {
26+ ...process.env,
27+OPENCLAW_HOME: path.join(homeDir, "home"),
28+OPENCLAW_BUNDLED_PLUGINS_DIR: undefined,
29+OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY:
30+options.disablePersistedRegistry === false ? undefined : "1",
31+OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1",
32+OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE: "1",
33+OPENCLAW_VERSION: "2026.4.25",
34+VITEST: "true",
35+};
36+}
37+38+function createColdControlPlanePlugin() {
39+const rootDir = makeTempDir();
40+const runtimeMarker = path.join(rootDir, "runtime-loaded.txt");
41+fs.writeFileSync(
42+path.join(rootDir, "package.json"),
43+JSON.stringify(
44+{
45+name: "@example/openclaw-cold-control-plane",
46+version: "1.0.0",
47+openclaw: { extensions: ["./index.cjs"] },
48+},
49+null,
50+2,
51+),
52+"utf8",
53+);
54+fs.writeFileSync(
55+path.join(rootDir, "openclaw.plugin.json"),
56+JSON.stringify(
57+{
58+id: "cold-control-plane",
59+name: "Cold Control Plane",
60+configSchema: { type: "object" },
61+providers: ["cold-model-provider"],
62+channels: ["cold-channel"],
63+channelConfigs: {
64+"cold-channel": {
65+schema: { type: "object" },
66+},
67+},
68+providerAuthChoices: [
69+{
70+provider: "cold-model-provider",
71+method: "api-key",
72+choiceId: "cold-provider-api-key",
73+choiceLabel: "Cold Provider API key",
74+groupId: "cold-model-provider",
75+groupLabel: "Cold Provider",
76+optionKey: "coldProviderApiKey",
77+cliFlag: "--cold-provider-api-key",
78+cliOption: "--cold-provider-api-key <key>",
79+onboardingScopes: ["text-inference"],
80+},
81+],
82+},
83+null,
84+2,
85+),
86+"utf8",
87+);
88+fs.writeFileSync(
89+path.join(rootDir, "index.cjs"),
90+`require("node:fs").writeFileSync(${JSON.stringify(runtimeMarker)}, "loaded", "utf8");\nthrow new Error("runtime entry should not load for command control-plane discovery");\n`,
91+"utf8",
92+);
93+return { rootDir, runtimeMarker };
94+}
95+96+function createColdConfig(pluginDir: string): OpenClawConfig {
97+return {
98+plugins: {
99+load: { paths: [pluginDir] },
100+entries: {
101+"cold-control-plane": { enabled: true },
102+},
103+},
104+};
105+}
106+107+afterEach(() => {
108+clearPluginDiscoveryCache();
109+clearPluginManifestRegistryCache();
110+for (const dir of tempDirs.splice(0)) {
111+fs.rmSync(dir, { recursive: true, force: true });
112+}
113+});
114+115+describe("command control-plane plugin discovery", () => {
116+it("resolves channel setup metadata without importing plugin runtime", () => {
117+const plugin = createColdControlPlanePlugin();
118+const workspaceDir = makeTempDir();
119+const cfg = createColdConfig(plugin.rootDir);
120+const env = hermeticEnv(workspaceDir);
121+122+expect(
123+listManifestInstalledChannelIds({
124+ cfg,
125+ workspaceDir,
126+ env,
127+}),
128+).toContain("cold-channel");
129+expect(fs.existsSync(plugin.runtimeMarker)).toBe(false);
130+});
131+132+it("builds onboarding auth choices from manifest metadata without importing plugin runtime", () => {
133+const plugin = createColdControlPlanePlugin();
134+const workspaceDir = makeTempDir();
135+const cfg = createColdConfig(plugin.rootDir);
136+const env = hermeticEnv(workspaceDir);
137+138+expect(
139+buildAuthChoiceOptions({
140+store: {} as never,
141+includeSkip: false,
142+config: cfg,
143+ workspaceDir,
144+ env,
145+}),
146+).toContainEqual(
147+expect.objectContaining({
148+value: "cold-provider-api-key",
149+label: "Cold Provider API key",
150+groupId: "cold-model-provider",
151+}),
152+);
153+expect(
154+formatAuthChoiceChoicesForCli({
155+config: cfg,
156+ workspaceDir,
157+ env,
158+}).split("|"),
159+).toContain("cold-provider-api-key");
160+expect(fs.existsSync(plugin.runtimeMarker)).toBe(false);
161+});
162+163+it("resolves models-list provider ownership without importing plugin runtime", async () => {
164+const plugin = createColdControlPlanePlugin();
165+const workspaceDir = makeTempDir();
166+const cfg = createColdConfig(plugin.rootDir);
167+const env = hermeticEnv(workspaceDir, { disablePersistedRegistry: false });
168+169+await refreshPluginRegistry({
170+config: cfg,
171+ workspaceDir,
172+ env,
173+reason: "manual",
174+});
175+expect(fs.existsSync(plugin.runtimeMarker)).toBe(false);
176+177+await expect(
178+resolveProviderCatalogPluginIdsForFilter({
179+ cfg,
180+ env,
181+providerFilter: "cold-model-provider",
182+}),
183+).resolves.toEqual(["cold-control-plane"]);
184+expect(fs.existsSync(plugin.runtimeMarker)).toBe(false);
185+});
186+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。