Files
openclaw/src/plugins/plugin-metadata-snapshot.memo.test.ts
Peter Steinberger 1d21224de3 perf: reduce runtime metadata hotpath churn
Reduce runtime metadata hotpath churn by freezing loaded plugin metadata snapshots once and returning the memoized object without clone-on-hit. Reuse persisted package file signatures while preserving realpath containment, cache normalized Jiti alias maps by identity, and defer Discord realtime turn retention/logging until audio starts.

Verification:
- node scripts/run-vitest.mjs src/talk/turn-context-tracker.test.ts src/plugins/plugin-metadata-snapshot.memo.test.ts src/plugins/manifest-registry-installed.test.ts src/plugins/sdk-alias.test.ts src/plugins/installed-plugin-index-records.test.ts
- node scripts/run-vitest.mjs src/plugins/plugin-metadata-snapshot.memo.test.ts
- pnpm test extensions/discord/src/voice/manager.e2e.test.ts --testNamePattern "keeps realtime playback alive|interrupts realtime playback|does not interrupt realtime provider state"
- pnpm lint --threads=8
- pnpm exec oxfmt --check src/plugins/plugin-metadata-snapshot.ts src/plugins/plugin-metadata-snapshot.memo.test.ts src/plugins/manifest-registry-installed.ts src/plugins/installed-plugin-index-record-builder.ts src/plugins/sdk-alias.ts extensions/discord/src/voice/realtime.ts
- pnpm tsgo:core
- pnpm tsgo:extensions
- pnpm build
- autoreview --mode commit --commit HEAD
- PR CI green on head 7dd3e44a78
2026-05-25 23:59:45 +01:00

768 lines
27 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
clearCurrentPluginMetadataSnapshot,
setCurrentPluginMetadataSnapshot,
} from "./current-plugin-metadata-snapshot.js";
import { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js";
import type { InstalledPluginIndex } from "./installed-plugin-index.js";
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
import { clearPluginMetadataLifecycleCaches } from "./plugin-metadata-lifecycle.js";
import {
clearLoadPluginMetadataSnapshotMemo,
loadPluginMetadataSnapshot,
resolvePluginMetadataSnapshot,
} from "./plugin-metadata-snapshot.js";
const loadPluginRegistrySnapshotWithMetadata = vi.hoisted(() => vi.fn());
const loadPluginManifestRegistryForInstalledIndex = vi.hoisted(() => vi.fn());
vi.mock("./plugin-registry.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./plugin-registry.js")>();
return {
...actual,
loadPluginRegistrySnapshotWithMetadata: (params: unknown) =>
loadPluginRegistrySnapshotWithMetadata(params),
};
});
vi.mock("./manifest-registry-installed.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./manifest-registry-installed.js")>();
return {
...actual,
loadPluginManifestRegistryForInstalledIndex: (params: unknown) =>
loadPluginManifestRegistryForInstalledIndex(params),
};
});
const tempDirs: string[] = [];
function tempStateDir(): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-metadata-memo-"));
tempDirs.push(dir);
return dir;
}
function touchPersistedIndex(stateDir: string, value = 1): void {
const indexPath = path.join(stateDir, "plugins", "installs.json");
fs.mkdirSync(path.dirname(indexPath), { recursive: true });
fs.writeFileSync(indexPath, JSON.stringify({ value }));
}
function writeJson(filePath: string, value: unknown): void {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, `${JSON.stringify(value)}\n`);
}
function writePersistedIndex(params: {
manifestPath?: string;
packageJsonPath?: string;
pluginId: string;
source?: string;
setupSource?: string;
stateDir: string;
}): void {
const pluginDir = path.join(params.stateDir, "extensions", params.pluginId);
const manifestPath = params.manifestPath ?? path.join(pluginDir, "openclaw.plugin.json");
const packageJsonPath = params.packageJsonPath ?? path.join(pluginDir, "package.json");
writeJson(path.join(params.stateDir, "plugins", "installs.json"), {
version: 1,
hostContractVersion: "test",
compatRegistryVersion: "test",
migrationVersion: 1,
policyHash: "test",
generatedAtMs: 1,
installRecords: {},
diagnostics: [],
plugins: [
{
pluginId: params.pluginId,
manifestPath,
manifestHash: `${params.pluginId}-manifest`,
rootDir: pluginDir,
...(params.source ? { source: params.source } : {}),
...(params.setupSource ? { setupSource: params.setupSource } : {}),
origin: "global",
enabled: true,
packageJson: { path: "package.json", hash: `${params.pluginId}-package` },
startup: {
sidecar: false,
memory: false,
deferConfiguredChannelFullLoadUntilAfterListen: false,
agentHarnesses: [],
},
compat: [],
},
],
});
writeJson(manifestPath, { id: params.pluginId });
writeJson(packageJsonPath, { name: params.pluginId });
}
function writeRecoverableNpmPlugin(params: {
packageName: string;
pluginId: string;
stateDir: string;
version: string;
writeRootManifest?: boolean;
}): void {
const packageDir = path.join(params.stateDir, "npm", "node_modules", params.packageName);
if (params.writeRootManifest !== false) {
writeJson(path.join(params.stateDir, "npm", "package.json"), {
dependencies: {
[params.packageName]: "1.0.0",
},
});
}
writeJson(path.join(packageDir, "package.json"), {
name: params.packageName,
version: params.version,
openclaw: {
extensions: ["."],
},
});
writeJson(path.join(packageDir, "openclaw.plugin.json"), { id: params.pluginId });
}
function writePersistedInstallRecords(
stateDir: string,
installRecords: Record<string, Record<string, unknown>>,
): void {
writeJson(path.join(stateDir, "plugins", "installs.json"), {
version: 1,
hostContractVersion: "test",
compatRegistryVersion: "test",
migrationVersion: 1,
policyHash: "test",
generatedAtMs: 1,
installRecords,
diagnostics: [],
plugins: [],
});
}
function makeIndex(
pluginId = "demo",
options: {
manifestPath?: string;
rootDir?: string;
} = {},
): InstalledPluginIndex {
const rootDir = options.rootDir ?? `/plugins/${pluginId}`;
const manifestPath = options.manifestPath ?? path.join(rootDir, "openclaw.plugin.json");
return {
version: 1,
hostContractVersion: "test",
compatRegistryVersion: "test",
migrationVersion: 1,
policyHash: "test",
generatedAtMs: 1,
installRecords: {},
diagnostics: [],
plugins: [
{
pluginId,
manifestPath,
manifestHash: `${pluginId}-manifest`,
rootDir,
origin: "global",
enabled: true,
startup: {
sidecar: false,
memory: false,
deferConfiguredChannelFullLoadUntilAfterListen: false,
agentHarnesses: [],
},
compat: [],
},
],
};
}
function makeManifestRegistry(pluginId = "demo"): PluginManifestRegistry {
const plugin: PluginManifestRecord = {
id: pluginId,
name: pluginId,
channels: [],
providers: [pluginId],
cliBackends: [],
skills: [],
hooks: [],
commandAliases: [{ name: `${pluginId}-command` }],
rootDir: `/plugins/${pluginId}`,
source: `/plugins/${pluginId}/index.js`,
manifestPath: `/plugins/${pluginId}/openclaw.plugin.json`,
origin: "global",
};
return { plugins: [plugin], diagnostics: [] };
}
describe("loadPluginMetadataSnapshot process memo", () => {
beforeEach(() => {
clearLoadPluginMetadataSnapshotMemo();
loadPluginRegistrySnapshotWithMetadata.mockReset();
loadPluginManifestRegistryForInstalledIndex.mockReset();
loadPluginManifestRegistryForInstalledIndex.mockReturnValue(makeManifestRegistry());
});
afterEach(() => {
clearLoadPluginMetadataSnapshotMemo();
clearCurrentPluginMetadataSnapshot();
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
it("reuses persisted metadata snapshots for repeated process lookups", () => {
const stateDir = tempStateDir();
touchPersistedIndex(stateDir);
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
const first = loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
const second = loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
const third = loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledOnce();
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
expect(second).toBe(first);
expect(third).toBe(first);
expect(() => third.plugins[0]?.providers.push("mutated")).toThrow();
expect(() => {
if (third.plugins[0]?.commandAliases?.[0]) {
third.plugins[0].commandAliases[0].name = "mutated";
}
}).toThrow();
expect(third.plugins[0]?.providers).toEqual(["demo"]);
expect(third.plugins[0]?.commandAliases?.[0]?.name).toBe("demo-command");
expect(second.manifestRegistry.plugins[0]).toBe(second.plugins[0]);
expect(second.byPluginId.get("demo")).toBe(second.plugins[0]);
});
it("clears the process memo at plugin metadata lifecycle boundaries", () => {
const stateDir = tempStateDir();
touchPersistedIndex(stateDir);
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
clearPluginMetadataLifecycleCaches();
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledTimes(2);
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledTimes(2);
});
it("keeps hot persisted snapshots for alternating config callers", () => {
const stateDir = tempStateDir();
touchPersistedIndex(stateDir);
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
loadPluginMetadataSnapshot({
config: { plugins: { allow: ["demo"] } },
env: {},
stateDir,
});
loadPluginMetadataSnapshot({
config: { plugins: { allow: ["other"] } },
env: {},
stateDir,
});
loadPluginMetadataSnapshot({
config: { plugins: { allow: ["demo"] } },
env: {},
stateDir,
});
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledTimes(2);
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledTimes(2);
});
it("reuses workspace-scoped current snapshots when the caller opts in", () => {
const index = makeIndex();
index.policyHash = resolveInstalledPluginIndexPolicyHash({});
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "runtime",
snapshot: index,
diagnostics: [],
});
const snapshot = loadPluginMetadataSnapshot({
config: {},
env: {},
index,
workspaceDir: "/workspace/a",
});
setCurrentPluginMetadataSnapshot(snapshot, {
config: {},
env: {},
workspaceDir: "/workspace/a",
});
loadPluginRegistrySnapshotWithMetadata.mockClear();
loadPluginManifestRegistryForInstalledIndex.mockClear();
expect(
resolvePluginMetadataSnapshot({
config: {},
env: {},
allowWorkspaceScopedCurrent: true,
}),
).toBe(snapshot);
expect(loadPluginRegistrySnapshotWithMetadata).not.toHaveBeenCalled();
expect(loadPluginManifestRegistryForInstalledIndex).not.toHaveBeenCalled();
});
it("reuses compatible current snapshots without reloading metadata", () => {
const sourceConfig = { plugins: { allow: ["demo"] } };
const compatibleConfig = { plugins: { entries: { demo: { enabled: true } } } };
const index = makeIndex();
index.policyHash = resolveInstalledPluginIndexPolicyHash(sourceConfig);
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "runtime",
snapshot: index,
diagnostics: [],
});
const snapshot = loadPluginMetadataSnapshot({
config: sourceConfig,
env: {},
index,
workspaceDir: "/workspace/a",
});
setCurrentPluginMetadataSnapshot(snapshot, {
config: sourceConfig,
compatibleConfigs: [compatibleConfig],
env: {},
workspaceDir: "/workspace/a",
});
loadPluginRegistrySnapshotWithMetadata.mockClear();
loadPluginManifestRegistryForInstalledIndex.mockClear();
expect(
resolvePluginMetadataSnapshot({
config: compatibleConfig,
env: {},
workspaceDir: "/workspace/a",
}),
).toBe(snapshot);
expect(loadPluginRegistrySnapshotWithMetadata).not.toHaveBeenCalled();
expect(loadPluginManifestRegistryForInstalledIndex).not.toHaveBeenCalled();
});
it("does not scan persisted registry files when the caller provides an index", () => {
const stateDir = tempStateDir();
writePersistedIndex({ pluginId: "demo", stateDir });
const index = makeIndex();
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "provided",
snapshot: index,
diagnostics: [],
});
loadPluginMetadataSnapshot({ config: {}, env: {}, index, stateDir });
const statSpy = vi.spyOn(fs, "statSync");
const readSpy = vi.spyOn(fs, "readFileSync");
try {
loadPluginMetadataSnapshot({ config: {}, env: {}, index, stateDir });
} finally {
statSpy.mockRestore();
readSpy.mockRestore();
}
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledOnce();
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
expect(statSpy).not.toHaveBeenCalled();
expect(readSpy).not.toHaveBeenCalled();
});
it("does not freeze caller-owned provided index records", () => {
const stateDir = tempStateDir();
const index = makeIndex();
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "provided",
snapshot: index,
diagnostics: [],
});
const snapshot = loadPluginMetadataSnapshot({ config: {}, env: {}, index, stateDir });
const callerRecord = index.plugins[0];
const snapshotRecord = snapshot.index.plugins[0];
if (!callerRecord || !snapshotRecord) {
throw new Error("expected metadata records");
}
expect(() => {
callerRecord.pluginId = "caller-mutated";
callerRecord.startup.agentHarnesses = ["caller-mutated"];
}).not.toThrow();
expect(snapshot.index.plugins[0]?.pluginId).toBe("demo");
expect(snapshot.index.plugins[0]?.startup.agentHarnesses).toEqual([]);
expect(() => {
snapshotRecord.pluginId = "snapshot-mutated";
}).toThrow();
});
it("memoizes policy-stale derived snapshots within the process", () => {
const stateDir = tempStateDir();
touchPersistedIndex(stateDir);
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "derived",
snapshot: makeIndex(),
diagnostics: [
{
level: "warn",
code: "persisted-registry-stale-policy",
message: "policy changed",
},
],
});
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledOnce();
});
it("keeps process-stable derived snapshots when derived plugin files change", () => {
const stateDir = tempStateDir();
touchPersistedIndex(stateDir);
const pluginDir = path.join(stateDir, "current", "derived");
const manifestPath = path.join(pluginDir, "openclaw.plugin.json");
writeJson(manifestPath, { id: "derived", version: "1.0.0" });
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "derived",
snapshot: makeIndex("derived", { manifestPath, rootDir: pluginDir }),
diagnostics: [
{
level: "warn",
code: "persisted-registry-stale-policy",
message: "policy changed",
},
],
});
loadPluginManifestRegistryForInstalledIndex.mockReturnValue(makeManifestRegistry("derived"));
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
writeJson(manifestPath, { id: "derived", version: "2.0.0", commandAliases: [{ name: "new" }] });
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledOnce();
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
});
it.each([
["persisted-registry-missing", undefined],
["persisted-registry-stale-source", undefined],
["persisted-registry-disabled", undefined],
[undefined, { preferPersisted: false }],
])("memoizes derived snapshots for %s diagnostics within the process", (code, options) => {
const stateDir = tempStateDir();
touchPersistedIndex(stateDir);
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "derived",
snapshot: makeIndex(),
diagnostics: code ? [{ level: "warn", code, message: "registry not reusable" }] : [],
});
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir, ...options });
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir, ...options });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledOnce();
});
it("refreshes when the persisted registry file changes", () => {
const stateDir = tempStateDir();
touchPersistedIndex(stateDir, 1);
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
touchPersistedIndex(stateDir, 22);
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledTimes(2);
});
it("reuses the expanded freshness fingerprint on hot cache hits", () => {
const stateDir = tempStateDir();
const manifestPath = path.join(stateDir, "extensions", "demo", "openclaw.plugin.json");
writePersistedIndex({ manifestPath, pluginId: "demo", stateDir });
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
const readSpy = vi.spyOn(fs, "readFileSync");
try {
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
} finally {
readSpy.mockRestore();
}
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledTimes(1);
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledTimes(1);
expect(readSpy).not.toHaveBeenCalled();
});
it.each([
["manifest", "openclaw.plugin.json", "manifestPath"],
["source", "index.js", "source"],
["setup source", "setup.js", "setupSource"],
["package manifest", "package.json", "packageJsonPath"],
])("requires reload before persisted plugin %s edits are visible", (_, fileName, field) => {
const stateDir = tempStateDir();
const filePath = path.join(stateDir, "extensions", "demo", fileName);
writePersistedIndex({ [field]: filePath, pluginId: "demo", stateDir });
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
writeJson(filePath, { id: "demo", version: "0.2.0" });
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledOnce();
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
});
it.each([
[
"install path package manifest",
"~/tracked-plugin",
(recordPath: string) => ({ source: "path", installPath: recordPath }),
(homeDir: string) => path.join(homeDir, "tracked-plugin", "package.json"),
],
[
"source path package manifest",
"~/tracked-plugin",
(recordPath: string) => ({ source: "path", sourcePath: recordPath }),
(homeDir: string) => path.join(homeDir, "tracked-plugin", "package.json"),
],
])(
"requires reload before home-relative install record %s changes are visible",
(_, recordPath, record, targetPath) => {
const stateDir = tempStateDir();
const homeDir = path.join(stateDir, "home");
const filePath = targetPath(homeDir);
writePersistedInstallRecords(stateDir, { demo: record(recordPath) });
writeJson(filePath, { version: "1.0.0" });
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
loadPluginMetadataSnapshot({ config: {}, env: { HOME: homeDir }, stateDir });
writeJson(filePath, { version: "1.0.1000" });
loadPluginMetadataSnapshot({ config: {}, env: { HOME: homeDir }, stateDir });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledOnce();
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
},
);
it("does not reuse home-relative install record memo state across env changes", () => {
const stateDir = tempStateDir();
const firstHomeDir = path.join(stateDir, "first-home");
const secondHomeDir = path.join(stateDir, "second-home");
const firstPackageJsonPath = path.join(firstHomeDir, "tracked-plugin", "package.json");
const secondPackageJsonPath = path.join(secondHomeDir, "tracked-plugin", "package.json");
writePersistedInstallRecords(stateDir, {
demo: { source: "path", installPath: "~/tracked-plugin" },
});
writeJson(firstPackageJsonPath, { version: "1.0.0" });
writeJson(secondPackageJsonPath, { version: "1.0.0" });
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
loadPluginMetadataSnapshot({ config: {}, env: { HOME: firstHomeDir }, stateDir });
loadPluginMetadataSnapshot({ config: {}, env: { HOME: secondHomeDir }, stateDir });
writeJson(secondPackageJsonPath, { version: "1.0.1000" });
loadPluginMetadataSnapshot({ config: {}, env: { HOME: secondHomeDir }, stateDir });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledTimes(2);
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledTimes(2);
});
it("requires reload before recovered managed npm package metadata changes are visible", () => {
const stateDir = tempStateDir();
writeRecoverableNpmPlugin({
packageName: "recovered-plugin",
pluginId: "recovered",
stateDir,
version: "1.0.0",
});
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
writeRecoverableNpmPlugin({
packageName: "recovered-plugin",
pluginId: "recovered",
stateDir,
version: "1.0.10",
writeRootManifest: false,
});
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledOnce();
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
});
it("requires reload before a declared recovered managed npm package appears", () => {
const stateDir = tempStateDir();
writeJson(path.join(stateDir, "npm", "package.json"), {
dependencies: {
"late-plugin": "1.0.0",
},
});
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
writeRecoverableNpmPlugin({
packageName: "late-plugin",
pluginId: "late-plugin",
stateDir,
version: "1.0.0",
writeRootManifest: false,
});
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledOnce();
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
});
it("requires reload before an in-root package manifest symlink target change is visible", () => {
const stateDir = tempStateDir();
const pluginDir = path.join(stateDir, "extensions", "demo");
const packageJsonPath = path.join(pluginDir, "package.json");
const outsidePackageJsonPath = path.join(stateDir, "outside", "package.json");
fs.mkdirSync(pluginDir, { recursive: true });
writeJson(outsidePackageJsonPath, { name: "outside", version: "1.0.0" });
fs.symlinkSync(outsidePackageJsonPath, packageJsonPath);
writePersistedIndex({ packageJsonPath, pluginId: "demo", stateDir });
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
writeJson(outsidePackageJsonPath, { name: "outside", version: "1.0.1" });
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
expect(loadPluginRegistrySnapshotWithMetadata).toHaveBeenCalledOnce();
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
});
it("does not fingerprint persisted plugin paths outside the plugin root", () => {
const stateDir = tempStateDir();
const outsideManifestPath = path.join(stateDir, "outside", "openclaw.plugin.json");
const outsideSourcePath = path.join(stateDir, "outside", "index.js");
writePersistedIndex({
manifestPath: outsideManifestPath,
pluginId: "demo",
source: outsideSourcePath,
stateDir,
});
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
const statSpy = vi.spyOn(fs, "statSync");
const readSpy = vi.spyOn(fs, "readFileSync");
try {
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
} finally {
statSpy.mockRestore();
readSpy.mockRestore();
}
expect(statSpy.mock.calls.some(([filePath]) => filePath === outsideManifestPath)).toBe(false);
expect(statSpy.mock.calls.some(([filePath]) => filePath === outsideSourcePath)).toBe(false);
expect(readSpy.mock.calls.some(([filePath]) => filePath === outsideManifestPath)).toBe(false);
expect(readSpy.mock.calls.some(([filePath]) => filePath === outsideSourcePath)).toBe(false);
});
it("does not hash symlinked persisted plugin files that escape the plugin root", () => {
const stateDir = tempStateDir();
const pluginDir = path.join(stateDir, "extensions", "demo");
const manifestPath = path.join(pluginDir, "openclaw.plugin.json");
const outsideManifestPath = path.join(stateDir, "outside", "openclaw.plugin.json");
fs.mkdirSync(pluginDir, { recursive: true });
writeJson(outsideManifestPath, { id: "outside" });
fs.symlinkSync(outsideManifestPath, manifestPath);
writeJson(path.join(stateDir, "plugins", "installs.json"), {
version: 1,
hostContractVersion: "test",
compatRegistryVersion: "test",
migrationVersion: 1,
policyHash: "test",
generatedAtMs: 1,
installRecords: {},
diagnostics: [],
plugins: [
{
pluginId: "demo",
manifestPath,
manifestHash: "demo-manifest",
rootDir: pluginDir,
origin: "global",
enabled: true,
startup: {
sidecar: false,
memory: false,
deferConfiguredChannelFullLoadUntilAfterListen: false,
agentHarnesses: [],
},
compat: [],
},
],
});
loadPluginRegistrySnapshotWithMetadata.mockReturnValue({
source: "persisted",
snapshot: makeIndex(),
diagnostics: [],
});
const readSpy = vi.spyOn(fs, "readFileSync");
try {
loadPluginMetadataSnapshot({ config: {}, env: {}, stateDir });
} finally {
readSpy.mockRestore();
}
expect(readSpy.mock.calls.some(([filePath]) => filePath === outsideManifestPath)).toBe(false);
});
});