



















@@ -1,4 +1,7 @@
1-import { beforeEach, describe, expect, it, vi } from "vitest";
1+import fs from "node:fs";
2+import os from "node:os";
3+import path from "node:path";
4+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2536const mocks = vi.hoisted(() => ({
47repairMissingConfiguredPluginInstalls: vi.fn(),
@@ -21,6 +24,8 @@ import {
2124} from "./post-core-plugin-convergence.js";
22252326describe("runPostCorePluginConvergence", () => {
27+const tempDirs: string[] = [];
28+2429beforeEach(() => {
2530vi.clearAllMocks();
2631mocks.repairMissingConfiguredPluginInstalls.mockResolvedValue({
@@ -31,6 +36,43 @@ describe("runPostCorePluginConvergence", () => {
3136mocks.runPluginPayloadSmokeCheck.mockResolvedValue({ checked: [], failures: [] });
3237});
333839+afterEach(() => {
40+for (const dir of tempDirs.splice(0)) {
41+fs.rmSync(dir, { recursive: true, force: true });
42+}
43+});
44+45+function makeTempDir(): string {
46+const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-post-core-convergence-"));
47+tempDirs.push(dir);
48+return dir;
49+}
50+51+function writeBundledPlugin(rootDir: string, pluginId: string): string {
52+const pluginDir = path.join(rootDir, pluginId);
53+fs.mkdirSync(pluginDir, { recursive: true });
54+fs.writeFileSync(path.join(pluginDir, "index.js"), "export default {};\n", "utf8");
55+fs.writeFileSync(
56+path.join(pluginDir, "openclaw.plugin.json"),
57+JSON.stringify({
58+id: pluginId,
59+name: pluginId,
60+version: "2026.5.20-beta.1",
61+configSchema: { type: "object" },
62+}),
63+"utf8",
64+);
65+fs.writeFileSync(
66+path.join(pluginDir, "package.json"),
67+JSON.stringify({
68+name: `@openclaw/${pluginId}`,
69+version: "2026.5.20-beta.1",
70+}),
71+"utf8",
72+);
73+return pluginDir;
74+}
75+3476it("calls repair with OPENCLAW_UPDATE_POST_CORE_CONVERGENCE=1 set", async () => {
3577const cfg = { plugins: { entries: {} } } as unknown as OpenClawConfig;
3678await runPostCorePluginConvergence({
@@ -121,6 +163,55 @@ describe("runPostCorePluginConvergence", () => {
121163});
122164});
123165166+it("prunes stale local bundled plugin shadows from baseline records before repair", async () => {
167+const bundledRoot = makeTempDir();
168+writeBundledPlugin(bundledRoot, "discord");
169+const baseline = {
170+discord: {
171+source: "path" as const,
172+installPath: path.join(makeTempDir(), "dist", "extensions", "discord"),
173+version: "2026.5.4-beta.3",
174+},
175+brave: { source: "npm" as const, installPath: "/p/brave" },
176+};
177+mocks.repairMissingConfiguredPluginInstalls.mockResolvedValue({
178+changes: [],
179+warnings: [],
180+records: { brave: baseline.brave },
181+});
182+const cfg = {
183+plugins: { entries: { discord: { enabled: true }, brave: { enabled: true } } },
184+} as unknown as OpenClawConfig;
185+186+const result = await runPostCorePluginConvergence({
187+ cfg,
188+env: {
189+OPENCLAW_BUNDLED_PLUGINS_DIR: bundledRoot,
190+OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR: "1",
191+VITEST: "true",
192+},
193+baselineInstallRecords: baseline,
194+});
195+196+expect(mocks.repairMissingConfiguredPluginInstalls).toHaveBeenCalledWith({
197+ cfg,
198+env: {
199+OPENCLAW_BUNDLED_PLUGINS_DIR: bundledRoot,
200+OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR: "1",
201+VITEST: "true",
202+OPENCLAW_COMPATIBILITY_HOST_VERSION: VERSION,
203+OPENCLAW_UPDATE_POST_CORE_CONVERGENCE: "1",
204+},
205+baselineRecords: {
206+brave: baseline.brave,
207+},
208+});
209+expect(result.changes).toEqual([
210+'Removed stale local bundled plugin install record "discord".',
211+]);
212+expect(result.installRecords).toEqual({ brave: baseline.brave });
213+});
214+124215it("flags errored=true and surfaces actionable guidance when repair warns", async () => {
125216mocks.repairMissingConfiguredPluginInstalls.mockResolvedValue({
126217changes: [],
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。