
























@@ -1,6 +1,8 @@
11import fs from "node:fs";
22import path from "node:path";
33import { afterEach, describe, expect, it } from "vitest";
4+import { writePersistedInstalledPluginIndexSync } from "./installed-plugin-index-store.js";
5+import { loadInstalledPluginIndex } from "./installed-plugin-index.js";
46import { refreshPluginRegistry } from "./plugin-registry.js";
57import { buildPluginRegistrySnapshotReport, buildPluginSnapshotReport } from "./status.js";
68import {
@@ -17,11 +19,120 @@ function makeTempDir() {
1719return makeTrackedTempDir("openclaw-plugin-status", tempDirs);
1820}
192122+function writeManagedNpmPlugin(params: {
23+stateDir: string;
24+packageName: string;
25+pluginId: string;
26+version: string;
27+dependencySpec?: string;
28+}): string {
29+const npmRoot = path.join(params.stateDir, "npm");
30+const rootManifestPath = path.join(npmRoot, "package.json");
31+fs.mkdirSync(npmRoot, { recursive: true });
32+const rootManifest = fs.existsSync(rootManifestPath)
33+ ? (JSON.parse(fs.readFileSync(rootManifestPath, "utf8")) as {
34+dependencies?: Record<string, string>;
35+})
36+ : {};
37+fs.writeFileSync(
38+rootManifestPath,
39+JSON.stringify(
40+{
41+ ...rootManifest,
42+private: true,
43+dependencies: {
44+ ...rootManifest.dependencies,
45+[params.packageName]: params.dependencySpec ?? params.version,
46+},
47+},
48+null,
49+2,
50+),
51+"utf8",
52+);
53+54+const packageDir = path.join(npmRoot, "node_modules", params.packageName);
55+fs.mkdirSync(path.join(packageDir, "dist"), { recursive: true });
56+fs.writeFileSync(
57+path.join(packageDir, "package.json"),
58+JSON.stringify({
59+name: params.packageName,
60+version: params.version,
61+openclaw: { extensions: ["./dist/index.js"] },
62+}),
63+"utf8",
64+);
65+fs.writeFileSync(
66+path.join(packageDir, "openclaw.plugin.json"),
67+JSON.stringify({
68+id: params.pluginId,
69+name: "WhatsApp",
70+configSchema: { type: "object" },
71+}),
72+"utf8",
73+);
74+fs.writeFileSync(path.join(packageDir, "dist", "index.js"), "export {};\n", "utf8");
75+return packageDir;
76+}
77+2078afterEach(() => {
2179cleanupTrackedTempDirs(tempDirs);
2280});
23812482describe("buildPluginRegistrySnapshotReport", () => {
83+it("keeps recovered managed npm plugins visible when the persisted registry is stale", () => {
84+const tempRoot = makeTempDir();
85+const stateDir = path.join(tempRoot, "state");
86+const env = {
87+ ...createColdPluginHermeticEnv(tempRoot, {
88+bundledPluginsDir: makeTempDir(),
89+disablePersistedRegistry: false,
90+}),
91+OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
92+OPENCLAW_STATE_DIR: stateDir,
93+};
94+const config = {
95+plugins: {
96+entries: {
97+whatsapp: { enabled: true },
98+},
99+},
100+};
101+const whatsappDir = writeManagedNpmPlugin({
102+ stateDir,
103+packageName: "@openclaw/whatsapp",
104+pluginId: "whatsapp",
105+version: "2026.5.2",
106+});
107+const staleIndex = loadInstalledPluginIndex({
108+ config,
109+ env,
110+installRecords: {},
111+});
112+expect(staleIndex.plugins.some((plugin) => plugin.pluginId === "whatsapp")).toBe(false);
113+writePersistedInstalledPluginIndexSync(staleIndex, { stateDir });
114+115+const report = buildPluginRegistrySnapshotReport({
116+ config,
117+ env,
118+});
119+120+expect(report.registrySource).toBe("derived");
121+expect(report.registryDiagnostics).toContainEqual(
122+expect.objectContaining({ code: "persisted-registry-stale-source" }),
123+);
124+expect(report.plugins).toEqual(
125+expect.arrayContaining([
126+expect.objectContaining({
127+id: "whatsapp",
128+name: "WhatsApp",
129+source: fs.realpathSync(path.join(whatsappDir, "dist", "index.js")),
130+status: "loaded",
131+}),
132+]),
133+);
134+});
135+25136it("reconstructs list metadata from indexed manifests without importing plugin runtime", () => {
26137const fixture = createColdPluginFixture({
27138rootDir: makeTempDir(),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。