Skip to content

Commit 1a6dd28

Browse files
Jasmine ZhangJasmine Zhang
authored andcommitted
fix: accept mixed source/dist bundled roots fixes #87730
1 parent 44e6caf commit 1a6dd28

2 files changed

Lines changed: 81 additions & 1 deletion

File tree

src/plugins/plugin-registry-snapshot.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,30 @@ function writePackagePlugin(rootDir: string, options: { configPaths?: readonly s
6767
);
6868
}
6969

70+
function writeBundledPlugin(rootDir: string, pluginId: string, entryPath: string) {
71+
fs.mkdirSync(rootDir, { recursive: true });
72+
fs.writeFileSync(path.join(rootDir, entryPath), "export default { register() {} };\n", "utf8");
73+
fs.writeFileSync(
74+
path.join(rootDir, "openclaw.plugin.json"),
75+
JSON.stringify({
76+
id: pluginId,
77+
name: pluginId,
78+
description: pluginId,
79+
configSchema: { type: "object" },
80+
}),
81+
"utf8",
82+
);
83+
fs.writeFileSync(
84+
path.join(rootDir, "package.json"),
85+
JSON.stringify({
86+
name: `@openclaw/${pluginId}`,
87+
version: "1.0.0",
88+
openclaw: { extensions: [`./${entryPath}`] },
89+
}),
90+
"utf8",
91+
);
92+
}
93+
7094
function createCandidate(rootDir: string, pluginId = "demo"): PluginCandidate {
7195
fs.mkdirSync(rootDir, { recursive: true });
7296
fs.writeFileSync(path.join(rootDir, "index.ts"), "export default { register() {} };\n", "utf8");
@@ -753,6 +777,40 @@ describe("loadPluginRegistrySnapshotWithMetadata", () => {
753777
expectDiagnosticsContainCode(result.diagnostics, "persisted-registry-stale-source");
754778
});
755779

780+
it("keeps mixed source-checkout bundled roots from the same checkout", () => {
781+
const tempRoot = makeTempDir();
782+
const packageRoot = path.join(tempRoot, "openclaw");
783+
const bundledRoot = path.join(packageRoot, "dist", "extensions");
784+
const sourceRoot = path.join(packageRoot, "extensions");
785+
const stateDir = path.join(tempRoot, "state");
786+
const env = {
787+
OPENCLAW_BUNDLED_PLUGINS_DIR: bundledRoot,
788+
OPENCLAW_STATE_DIR: stateDir,
789+
OPENCLAW_VERSION: "2026.4.26",
790+
VITEST: "true",
791+
};
792+
793+
fs.mkdirSync(path.join(packageRoot, "src"), { recursive: true });
794+
fs.writeFileSync(path.join(packageRoot, ".git"), "gitdir: /tmp/mock\n", "utf8");
795+
fs.writeFileSync(path.join(packageRoot, "pnpm-workspace.yaml"), "packages: []\n", "utf8");
796+
writeBundledPlugin(path.join(bundledRoot, "codex"), "codex", "index.js");
797+
writeBundledPlugin(path.join(sourceRoot, "whatsapp"), "whatsapp", "index.ts");
798+
799+
const index = loadInstalledPluginIndex({ config: {}, env, stateDir });
800+
expect(index.plugins.map((plugin) => plugin.pluginId)).toEqual(["codex", "whatsapp"]);
801+
expect(index.plugins.map((plugin) => plugin.rootDir)).toEqual([
802+
fs.realpathSync(path.join(bundledRoot, "codex")),
803+
fs.realpathSync(path.join(sourceRoot, "whatsapp")),
804+
]);
805+
writePersistedInstalledPluginIndexSync(index, { stateDir });
806+
807+
const result = loadPluginRegistrySnapshotWithMetadata({ config: {}, env, stateDir });
808+
809+
expect(result.source).toBe("persisted");
810+
expect(result.diagnostics).toStrictEqual([]);
811+
expect(result.snapshot.plugins.map((plugin) => plugin.pluginId)).toEqual(["codex", "whatsapp"]);
812+
});
813+
756814
it("treats persisted registry as stale when a plugin diagnostic source path no longer exists", () => {
757815
const tempRoot = makeTempDir();
758816
const stateDir = path.join(tempRoot, "state");

src/plugins/plugin-registry-snapshot.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import path from "node:path";
55
import { resolveUserPath } from "../utils.js";
66
import { resolveCompatibilityHostVersion } from "../version.js";
77
import { resolveBundledPluginsDir } from "./bundled-dir.js";
8+
import { buildLegacyBundledRootPath } from "./bundled-load-path-aliases.js";
89
import { normalizePluginsConfig } from "./config-state.js";
910
import { getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot.js";
1011
import type { PluginDiscoveryResult } from "./discovery.js";
@@ -328,9 +329,30 @@ function hasMismatchedPersistedBundledPluginRoot(
328329
if (!bundledPluginsDir) {
329330
return false;
330331
}
332+
const allowedBundledRoots = resolveComparableBundledPluginRoots(bundledPluginsDir);
331333
return index.plugins.some(
332334
(plugin) =>
333-
plugin.origin === "bundled" && !isPathInsideOrEqual(plugin.rootDir, bundledPluginsDir),
335+
plugin.origin === "bundled" &&
336+
!allowedBundledRoots.some((allowedRoot) => isPathInsideOrEqual(plugin.rootDir, allowedRoot)),
337+
);
338+
}
339+
340+
function resolveComparableBundledPluginRoots(bundledPluginsDir: string): string[] {
341+
const roots = [resolveComparablePath(bundledPluginsDir)];
342+
const legacyRoot = buildLegacyBundledRootPath(bundledPluginsDir);
343+
if (legacyRoot && isSourceCheckoutBundledPluginRoot(legacyRoot)) {
344+
roots.push(resolveComparablePath(legacyRoot));
345+
}
346+
return [...new Set(roots)];
347+
}
348+
349+
function isSourceCheckoutBundledPluginRoot(extensionsDir: string): boolean {
350+
const packageRoot = path.dirname(extensionsDir);
351+
return (
352+
fs.existsSync(extensionsDir) &&
353+
fs.existsSync(path.join(packageRoot, ".git")) &&
354+
fs.existsSync(path.join(packageRoot, "pnpm-workspace.yaml")) &&
355+
fs.existsSync(path.join(packageRoot, "src"))
334356
);
335357
}
336358

0 commit comments

Comments
 (0)