mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(plugins): reuse lifecycle-owned metadata snapshot (#114807)
This commit is contained in:
committed by
GitHub
parent
6125edfb76
commit
8a2852bdfa
@@ -17,12 +17,9 @@ import {
|
||||
normalizePluginsConfig,
|
||||
resolveEffectivePluginActivationState,
|
||||
} from "../plugins/config-state.js";
|
||||
import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "../plugins/default-enablement.js";
|
||||
import {
|
||||
loadPluginManifestRegistry,
|
||||
type PluginManifestRecord,
|
||||
} from "../plugins/manifest-registry.js";
|
||||
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
|
||||
import { resolvePluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
|
||||
import { parseJsonWithJson5Fallback } from "../utils/parse-json-compat.js";
|
||||
import { ALWAYS_ALLOWED_RUNTIME_DIR_NAMES } from "./facade-activation-contract.js";
|
||||
import { resolveRegistryPluginModuleLocationFromRecords } from "./facade-resolution-shared.js";
|
||||
@@ -96,18 +93,11 @@ function getFacadeManifestRegistry(params: {
|
||||
}): readonly PluginManifestRecord[] {
|
||||
const envOption = params.env ? { env: params.env } : {};
|
||||
const resolved = getFacadeBoundaryResolvedConfig();
|
||||
const current = getCurrentPluginMetadataSnapshot({
|
||||
return resolvePluginMetadataSnapshot({
|
||||
config: resolved.config,
|
||||
...envOption,
|
||||
allowWorkspaceScopedSnapshot: true,
|
||||
});
|
||||
if (current?.manifestRegistry) {
|
||||
return current.manifestRegistry.plugins;
|
||||
}
|
||||
return loadPluginManifestRegistry({
|
||||
config: resolved.config,
|
||||
...envOption,
|
||||
}).plugins;
|
||||
allowWorkspaceScopedCurrent: true,
|
||||
}).manifestRegistry.plugins;
|
||||
}
|
||||
|
||||
/** Resolves the concrete plugin module location recorded in the manifest registry. */
|
||||
|
||||
@@ -40,9 +40,14 @@ export function resolvePluginLoadDiscovery(params: {
|
||||
suppliedManifestRegistry?: PluginManifestRegistry;
|
||||
}): ResolvedPluginLoadDiscovery {
|
||||
const { options, context } = params;
|
||||
const discovery = params.suppliedManifestRegistry
|
||||
// The load context has already verified workspace, environment, config, and
|
||||
// plugin scope against the current lifecycle-owned metadata generation.
|
||||
const suppliedManifestRegistry =
|
||||
params.suppliedManifestRegistry ??
|
||||
(options.discovery === undefined ? context.metadataSnapshot?.manifestRegistry : undefined);
|
||||
const discovery = suppliedManifestRegistry
|
||||
? {
|
||||
candidates: createPluginCandidatesFromManifestRegistry(params.suppliedManifestRegistry),
|
||||
candidates: createPluginCandidatesFromManifestRegistry(suppliedManifestRegistry),
|
||||
diagnostics: [] as PluginDiagnostic[],
|
||||
}
|
||||
: (options.discovery ??
|
||||
@@ -53,7 +58,7 @@ export function resolvePluginLoadDiscovery(params: {
|
||||
installRecords: context.installRecords,
|
||||
}));
|
||||
const manifestRegistry =
|
||||
params.suppliedManifestRegistry ??
|
||||
suppliedManifestRegistry ??
|
||||
loadPluginManifestRegistry({
|
||||
config: context.cfg,
|
||||
workspaceDir: options.workspaceDir,
|
||||
|
||||
@@ -352,6 +352,7 @@ export function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
||||
// scoped snapshots must match exactly to protect activation boundaries.
|
||||
const currentMetadataSnapshot =
|
||||
options.installRecords === undefined &&
|
||||
trustNormalized.loadPaths === normalized.loadPaths &&
|
||||
!shouldResolveRawConfigEnvVars &&
|
||||
(options.env === undefined || options.env === process.env)
|
||||
? (getCurrentPluginMetadataSnapshot({
|
||||
@@ -411,6 +412,7 @@ export function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
||||
return {
|
||||
env,
|
||||
cfg,
|
||||
metadataSnapshot: currentMetadataSnapshot,
|
||||
normalized: trustNormalized,
|
||||
activationSourceConfig,
|
||||
activationSource,
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { withEnv } from "../test-utils/env.js";
|
||||
import { clearPluginCommands } from "./command-registry-state.js";
|
||||
import { getPluginCommandSpecs } from "./command-specs.js";
|
||||
import { setCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot.js";
|
||||
import { getGlobalHookRunner, resetGlobalHookRunner } from "./hook-runner-global.js";
|
||||
import { writePersistedInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-records.js";
|
||||
import {
|
||||
@@ -51,6 +52,7 @@ import {
|
||||
globalAfterAll1,
|
||||
} from "./loader.test-harness.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
|
||||
import { createEmptyPluginRegistry } from "./registry.js";
|
||||
import {
|
||||
getActivePluginRegistry,
|
||||
@@ -441,6 +443,66 @@ describe("loadOpenClawPlugins", () => {
|
||||
expect(registry.plugins.find((entry) => entry.id === plugin.id)?.status).toBe("loaded");
|
||||
});
|
||||
|
||||
it("loads scoped plugins from the current metadata snapshot without rediscovering manifests", () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
id: "snapshot-manifest",
|
||||
body: `module.exports = { id: "snapshot-manifest", register() {} };`,
|
||||
});
|
||||
const config = {
|
||||
plugins: {
|
||||
load: { paths: [plugin.file] },
|
||||
allow: [plugin.id],
|
||||
},
|
||||
};
|
||||
const metadataSnapshot = loadPluginMetadataSnapshot({ config, env: process.env });
|
||||
setCurrentPluginMetadataSnapshot(metadataSnapshot, { config, env: process.env });
|
||||
fs.rmSync(path.join(plugin.dir, "openclaw.plugin.json"));
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config,
|
||||
onlyPluginIds: [plugin.id],
|
||||
});
|
||||
|
||||
expect(registry.plugins.find((entry) => entry.id === plugin.id)?.status).toBe("loaded");
|
||||
});
|
||||
|
||||
it("discovers plugin paths supplied only by the activation source", () => {
|
||||
useNoBundledPlugins();
|
||||
const snapshotPlugin = writePlugin({
|
||||
id: "snapshot-base",
|
||||
body: `module.exports = { id: "snapshot-base", register() {} };`,
|
||||
});
|
||||
const sourcePlugin = writePlugin({
|
||||
id: "activation-source-only",
|
||||
body: `module.exports = { id: "activation-source-only", register() {} };`,
|
||||
});
|
||||
const config = {
|
||||
plugins: {
|
||||
load: { paths: [snapshotPlugin.file] },
|
||||
allow: [snapshotPlugin.id, sourcePlugin.id],
|
||||
},
|
||||
};
|
||||
const metadataSnapshot = loadPluginMetadataSnapshot({ config, env: process.env });
|
||||
setCurrentPluginMetadataSnapshot(metadataSnapshot, { config, env: process.env });
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
activate: false,
|
||||
cache: false,
|
||||
config,
|
||||
activationSourceConfig: {
|
||||
plugins: {
|
||||
load: { paths: [sourcePlugin.file] },
|
||||
allow: [sourcePlugin.id],
|
||||
},
|
||||
},
|
||||
onlyPluginIds: [sourcePlugin.id],
|
||||
});
|
||||
|
||||
expect(registry.plugins.find((entry) => entry.id === sourcePlugin.id)?.status).toBe("loaded");
|
||||
});
|
||||
|
||||
it("loads installed plugin packages discovered from persisted install records", () => {
|
||||
useNoBundledPlugins();
|
||||
const stateDir = makeTempDir();
|
||||
|
||||
@@ -71,14 +71,17 @@ function setLoaderMetadataSnapshot(params: { pluginIds?: readonly string[] } = {
|
||||
|
||||
describe("resolvePluginLoadCacheContext", () => {
|
||||
it("reuses prepared install records from the compatible metadata generation", () => {
|
||||
const { config, env, installRecords, workspaceDir } = setLoaderMetadataSnapshot();
|
||||
const { config, env, installRecords, snapshot, workspaceDir } = setLoaderMetadataSnapshot();
|
||||
|
||||
expect(resolvePluginLoadCacheContext({ config, env, workspaceDir }).installRecords).toEqual(
|
||||
installRecords,
|
||||
);
|
||||
expect(resolvePluginLoadCacheContext({ config, workspaceDir }).installRecords).toEqual(
|
||||
installRecords,
|
||||
);
|
||||
for (const options of [
|
||||
{ config, env, workspaceDir },
|
||||
{ config, workspaceDir },
|
||||
]) {
|
||||
const context = resolvePluginLoadCacheContext(options);
|
||||
|
||||
expect(context.installRecords).toEqual(installRecords);
|
||||
expect(context.metadataSnapshot).toBe(snapshot);
|
||||
}
|
||||
});
|
||||
|
||||
it("loads a custom profile's install records instead of reusing the process snapshot", () => {
|
||||
@@ -101,9 +104,34 @@ describe("resolvePluginLoadCacheContext", () => {
|
||||
expect(resolvePluginLoadCacheContext({ config, env, workspaceDir }).installRecords).toEqual(
|
||||
installRecords,
|
||||
);
|
||||
expect(
|
||||
resolvePluginLoadCacheContext({ config, env: profileEnv, workspaceDir }).installRecords,
|
||||
).toEqual(profileInstallRecords);
|
||||
const profileContext = resolvePluginLoadCacheContext({
|
||||
config,
|
||||
env: profileEnv,
|
||||
workspaceDir,
|
||||
});
|
||||
|
||||
expect(profileContext.installRecords).toEqual(profileInstallRecords);
|
||||
expect(profileContext.metadataSnapshot).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not reuse metadata when the activation source adds plugin load paths", () => {
|
||||
const { config, env, workspaceDir } = setLoaderMetadataSnapshot();
|
||||
const activationSourceConfig: OpenClawConfig = {
|
||||
plugins: {
|
||||
...config.plugins,
|
||||
load: { paths: ["/plugins/activation-source-only"] },
|
||||
},
|
||||
};
|
||||
|
||||
const context = resolvePluginLoadCacheContext({
|
||||
activationSourceConfig,
|
||||
config,
|
||||
env,
|
||||
workspaceDir,
|
||||
});
|
||||
|
||||
expect(context.normalized.loadPaths).toContain("/plugins/activation-source-only");
|
||||
expect(context.metadataSnapshot).toBeUndefined();
|
||||
});
|
||||
|
||||
it("reuses an exact matching scoped metadata generation", () => {
|
||||
|
||||
Reference in New Issue
Block a user