Skip to content

Commit 86d7bea

Browse files
committed
fix: keep plugin registry memo fresh for installs
1 parent 365f551 commit 86d7bea

2 files changed

Lines changed: 48 additions & 0 deletions

File tree

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
22
import fs from "node:fs";
33
import path from "node:path";
44
import { afterEach, describe, expect, it, vi } from "vitest";
5+
import type { OpenClawConfig } from "../config/types.openclaw.js";
56
import {
67
clearCurrentPluginMetadataSnapshot,
78
setCurrentPluginMetadataSnapshot,
@@ -428,6 +429,32 @@ describe("loadPluginRegistrySnapshotWithMetadata", () => {
428429
expect(result.diagnostics).toStrictEqual([]);
429430
});
430431

432+
it("refreshes a memoized derived snapshot when workspace plugins are installed", () => {
433+
const tempRoot = makeTempDir();
434+
const workspaceDir = path.join(tempRoot, "workspace");
435+
const env = { ...createHermeticEnv(tempRoot), OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1" };
436+
437+
const first = loadPluginRegistrySnapshotWithMetadata({ config: {}, env, workspaceDir });
438+
expect(first.snapshot.plugins.map((plugin) => plugin.pluginId)).not.toContain("demo");
439+
440+
writePackagePlugin(path.join(workspaceDir, ".openclaw", "extensions", "demo"));
441+
442+
const second = loadPluginRegistrySnapshotWithMetadata({ config: {}, env, workspaceDir });
443+
expect(second.snapshot.plugins.map((plugin) => plugin.pluginId)).toContain("demo");
444+
});
445+
446+
it("ignores malformed load paths while fingerprinting memoized snapshots", () => {
447+
const tempRoot = makeTempDir();
448+
const env = { ...createHermeticEnv(tempRoot), OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1" };
449+
const config = {
450+
plugins: {
451+
load: { paths: "not-an-array" },
452+
},
453+
} as unknown as OpenClawConfig;
454+
455+
expect(() => loadPluginRegistrySnapshotWithMetadata({ config, env })).not.toThrow();
456+
});
457+
431458
it("keeps persisted package plugins when file hashes match", () => {
432459
const tempRoot = makeTempDir();
433460
const rootDir = path.join(tempRoot, "workspace");

src/plugins/plugin-registry-snapshot.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import path from "node:path";
44
import { resolveUserPath } from "../utils.js";
55
import { resolveCompatibilityHostVersion } from "../version.js";
66
import { resolveBundledPluginsDir } from "./bundled-dir.js";
7+
import { normalizePluginsConfig } from "./config-state.js";
78
import { getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot.js";
89
import type { PluginDiscoveryResult } from "./discovery.js";
910
import { fileSignatureMatches, hashJson } from "./installed-plugin-index-hash.js";
@@ -31,6 +32,7 @@ import {
3132
} from "./installed-plugin-index.js";
3233
import { registerPluginMetadataProcessMemoLifecycleClear } from "./plugin-metadata-lifecycle.js";
3334
import type { PluginRegistrySnapshotSource } from "./plugin-registry-snapshot.types.js";
35+
import { resolvePluginCacheInputs } from "./roots.js";
3436

3537
export type PluginRegistrySnapshot = InstalledPluginIndex;
3638
export type PluginRegistryRecord = InstalledPluginIndexRecord;
@@ -148,11 +150,30 @@ function resolvePluginRegistrySnapshotMemoKey(
148150
...(params.stateDir ? { stateDir: params.stateDir } : {}),
149151
}),
150152
),
153+
pluginRoots: fingerprintPluginSourceRoots(params, env),
151154
stateDir: params.stateDir ? resolveUserPath(params.stateDir, env) : null,
152155
workspaceDir: params.workspaceDir ? resolveUserPath(params.workspaceDir, env) : null,
153156
});
154157
}
155158

159+
function fingerprintPluginSourceRoots(
160+
params: LoadPluginRegistryParams,
161+
env: NodeJS.ProcessEnv,
162+
): unknown {
163+
const workspaceDir = params.workspaceDir ? resolveUserPath(params.workspaceDir, env) : undefined;
164+
const cacheInputs = resolvePluginCacheInputs({
165+
workspaceDir,
166+
loadPaths: normalizePluginsConfig(params.config?.plugins).loadPaths,
167+
env,
168+
});
169+
return {
170+
global: fileFingerprint(cacheInputs.roots.global),
171+
loadPaths: cacheInputs.loadPaths.map((entry) => fileFingerprint(entry)),
172+
stock: cacheInputs.roots.stock ? fileFingerprint(cacheInputs.roots.stock) : null,
173+
workspace: cacheInputs.roots.workspace ? fileFingerprint(cacheInputs.roots.workspace) : null,
174+
};
175+
}
176+
156177
function fileFingerprint(filePath: string): unknown {
157178
try {
158179
const stat = fs.statSync(filePath, { bigint: true });

0 commit comments

Comments
 (0)