

























@@ -1,7 +1,11 @@
11import fs from "node:fs";
2+import path from "node:path";
3+import { resolveCompatibilityHostVersion } from "../version.js";
4+import { resolveBundledPluginsDir } from "./bundled-dir.js";
25import {
36inspectPersistedInstalledPluginIndex,
47readPersistedInstalledPluginIndexSync,
8+resolveInstalledPluginIndexStorePath,
59refreshPersistedInstalledPluginIndex,
610type InstalledPluginIndexStoreInspection,
711type InstalledPluginIndexStoreOptions,
@@ -18,6 +22,7 @@ import {
1822type LoadInstalledPluginIndexParams,
1923type RefreshInstalledPluginIndexParams,
2024} from "./installed-plugin-index.js";
25+import { resolvePluginCacheInputs } from "./roots.js";
21262227export type PluginRegistrySnapshot = InstalledPluginIndex;
2328export type PluginRegistryRecord = InstalledPluginIndexRecord;
@@ -41,6 +46,16 @@ export type PluginRegistrySnapshotResult = {
4146diagnostics: readonly PluginRegistrySnapshotDiagnostic[];
4247};
434849+const DERIVED_SNAPSHOT_CACHE_MS = 1000;
50+const derivedSnapshotCache = new Map<
51+string,
52+{ expiresAt: number; result: PluginRegistrySnapshotResult }
53+>();
54+55+export function clearPluginRegistrySnapshotCache(): void {
56+derivedSnapshotCache.clear();
57+}
58+4459export const DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV = "OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY";
45604661function formatDeprecatedPersistedRegistryDisableWarning(): string {
@@ -76,6 +91,68 @@ function hasMissingPersistedPluginSource(index: InstalledPluginIndex): boolean {
7691});
7792}
789394+function resolveComparablePath(filePath: string): string {
95+try {
96+return fs.realpathSync(filePath);
97+} catch {
98+return path.resolve(filePath);
99+}
100+}
101+102+function isPathInsideOrEqual(childPath: string, parentPath: string): boolean {
103+const relative = path.relative(
104+resolveComparablePath(parentPath),
105+resolveComparablePath(childPath),
106+);
107+return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
108+}
109+110+function hasMismatchedPersistedBundledPluginRoot(
111+index: InstalledPluginIndex,
112+env: NodeJS.ProcessEnv,
113+): boolean {
114+const bundledPluginsDir = resolveBundledPluginsDir(env);
115+if (!bundledPluginsDir) {
116+return false;
117+}
118+return index.plugins.some(
119+(plugin) =>
120+plugin.origin === "bundled" && !isPathInsideOrEqual(plugin.rootDir, bundledPluginsDir),
121+);
122+}
123+124+function resolveDerivedSnapshotCacheKey(
125+params: LoadPluginRegistryParams,
126+env: NodeJS.ProcessEnv,
127+): string | null {
128+if (
129+params.cache === false ||
130+params.preferPersisted === false ||
131+params.config ||
132+params.workspaceDir ||
133+params.stateDir ||
134+params.filePath ||
135+params.pluginIndexFilePath ||
136+params.installRecords ||
137+params.candidates ||
138+params.diagnostics ||
139+params.now
140+) {
141+return null;
142+}
143+144+const { roots, loadPaths } = resolvePluginCacheInputs({ env });
145+return JSON.stringify({
146+persistedStore: resolveInstalledPluginIndexStorePath({ env }),
147+ roots,
148+ loadPaths,
149+hostContractVersion: resolveCompatibilityHostVersion(env),
150+disablePersisted: env[DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV] ?? "",
151+disableBundled: env.OPENCLAW_DISABLE_BUNDLED_PLUGINS ?? "",
152+vitest: env.VITEST ?? "",
153+});
154+}
155+79156export function loadPluginRegistrySnapshotWithMetadata(
80157params: LoadPluginRegistryParams = {},
81158): PluginRegistrySnapshotResult {
@@ -93,6 +170,15 @@ export function loadPluginRegistrySnapshotWithMetadata(
93170const disabledByEnv = hasEnvFlag(env, DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV);
94171const persistedReadsEnabled = !disabledByCaller && !disabledByEnv;
95172const persistedInstallRecordReadsEnabled = !disabledByEnv;
173+const derivedCacheKey = persistedReadsEnabled
174+ ? resolveDerivedSnapshotCacheKey(params, env)
175+ : null;
176+if (derivedCacheKey) {
177+const cached = derivedSnapshotCache.get(derivedCacheKey);
178+if (cached && cached.expiresAt > Date.now()) {
179+return cached.result;
180+}
181+}
96182let persistedIndex: InstalledPluginIndex | null = null;
97183if (persistedInstallRecordReadsEnabled) {
98184persistedIndex = readPersistedInstalledPluginIndexSync(params);
@@ -114,6 +200,13 @@ export function loadPluginRegistrySnapshotWithMetadata(
114200message:
115201"Persisted plugin registry points at missing plugin files; using derived plugin index. Run `openclaw plugins registry --refresh` to update the persisted registry.",
116202});
203+} else if (hasMismatchedPersistedBundledPluginRoot(persistedIndex, env)) {
204+diagnostics.push({
205+level: "warn",
206+code: "persisted-registry-stale-source",
207+message:
208+"Persisted plugin registry points at a different bundled plugin tree; using derived plugin index. Run `openclaw plugins registry --refresh` to update the persisted registry.",
209+});
117210} else {
118211return {
119212snapshot: persistedIndex,
@@ -138,7 +231,7 @@ export function loadPluginRegistrySnapshotWithMetadata(
138231});
139232}
140233141-return {
234+const result: PluginRegistrySnapshotResult = {
142235snapshot: loadInstalledPluginIndex({
143236 ...params,
144237installRecords:
@@ -148,6 +241,13 @@ export function loadPluginRegistrySnapshotWithMetadata(
148241source: "derived",
149242 diagnostics,
150243};
244+if (derivedCacheKey) {
245+derivedSnapshotCache.set(derivedCacheKey, {
246+expiresAt: Date.now() + DERIVED_SNAPSHOT_CACHE_MS,
247+ result,
248+});
249+}
250+return result;
151251}
152252153253function resolveSnapshot(params: LoadPluginRegistryParams = {}): PluginRegistrySnapshot {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。