mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-27 13:38:56 +00:00
perf(gateway): cache current plugin metadata fingerprints
This commit is contained in:
@@ -177,6 +177,34 @@ describe("current plugin metadata snapshot", () => {
|
||||
expect(getCurrentPluginMetadataSnapshot({ config, env: requestedEnv })).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects an exact cached config object after in-place policy changes", () => {
|
||||
const config = { plugins: { allow: ["demo"] } };
|
||||
const snapshot = createSnapshot({ config });
|
||||
setCurrentPluginMetadataSnapshot(snapshot, { config });
|
||||
|
||||
expect(getCurrentPluginMetadataSnapshot({ config })).toBe(snapshot);
|
||||
|
||||
config.plugins.allow = ["other"];
|
||||
|
||||
expect(getCurrentPluginMetadataSnapshot({ config })).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects an exact cached env object after in-place root changes", () => {
|
||||
const config = {};
|
||||
const snapshot = createSnapshot({ config });
|
||||
const env = {
|
||||
HOME: "/home/snapshot",
|
||||
OPENCLAW_HOME: undefined,
|
||||
} as NodeJS.ProcessEnv;
|
||||
setCurrentPluginMetadataSnapshot(snapshot, { config, env });
|
||||
|
||||
expect(getCurrentPluginMetadataSnapshot({ config, env })).toBe(snapshot);
|
||||
|
||||
env.HOME = "/home/requested";
|
||||
|
||||
expect(getCurrentPluginMetadataSnapshot({ config, env })).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps source-policy compatibility when storing an auto-enabled runtime config", () => {
|
||||
const sourceConfig = { channels: { telegram: { botToken: "token" } } };
|
||||
const autoEnabledConfig = {
|
||||
|
||||
@@ -10,8 +10,20 @@ import {
|
||||
type ResolvePluginControlPlaneContextParams,
|
||||
} from "./plugin-control-plane-context.js";
|
||||
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js";
|
||||
import { resolvePluginCacheInputs } from "./roots.js";
|
||||
|
||||
type CurrentPluginMetadataSnapshotState = ReturnType<typeof getCurrentPluginMetadataSnapshotState>;
|
||||
type CurrentPluginMetadataConfigCacheEntry = {
|
||||
configFingerprint: string;
|
||||
discoveryKey: string;
|
||||
policyHash: string;
|
||||
policyKey: string;
|
||||
};
|
||||
|
||||
let currentPluginMetadataConfigIdentityCache = new WeakMap<
|
||||
OpenClawConfig,
|
||||
CurrentPluginMetadataConfigCacheEntry
|
||||
>();
|
||||
|
||||
export function resolvePluginMetadataControlPlaneFingerprint(
|
||||
config?: OpenClawConfig,
|
||||
@@ -29,6 +41,65 @@ export function isReusableCurrentPluginMetadataSnapshot(
|
||||
return true;
|
||||
}
|
||||
|
||||
function resolveConfiguredPluginLoadPaths(
|
||||
config: OpenClawConfig | undefined,
|
||||
): readonly string[] | undefined {
|
||||
const paths = config?.plugins?.load?.paths;
|
||||
return Array.isArray(paths) ? paths : undefined;
|
||||
}
|
||||
|
||||
function resolveCurrentPluginMetadataDiscoveryKey(params: {
|
||||
config?: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
workspaceDir?: string;
|
||||
}): string {
|
||||
return JSON.stringify(
|
||||
resolvePluginCacheInputs({
|
||||
env: params.env,
|
||||
workspaceDir: params.workspaceDir,
|
||||
loadPaths: [...(resolveConfiguredPluginLoadPaths(params.config) ?? [])],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function resolveCurrentPluginMetadataPolicyKey(config: OpenClawConfig | undefined): string {
|
||||
const plugins = config?.plugins;
|
||||
const rawEntries = plugins?.entries;
|
||||
const entries: Record<string, unknown> = {};
|
||||
if (rawEntries && typeof rawEntries === "object" && !Array.isArray(rawEntries)) {
|
||||
for (const [pluginId, entry] of Object.entries(rawEntries)) {
|
||||
if (entry && typeof entry === "object" && !Array.isArray(entry)) {
|
||||
const enabled = (entry as Record<string, unknown>).enabled;
|
||||
if (enabled !== undefined) {
|
||||
entries[pluginId] = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const channelPolicy: Record<string, boolean> = {};
|
||||
const channels = config?.channels;
|
||||
if (channels && typeof channels === "object" && !Array.isArray(channels)) {
|
||||
for (const [channelId, value] of Object.entries(channels)) {
|
||||
if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||
const enabled = (value as Record<string, unknown>).enabled;
|
||||
if (typeof enabled === "boolean") {
|
||||
channelPolicy[channelId] = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return JSON.stringify({
|
||||
plugins: {
|
||||
enabled: plugins?.enabled,
|
||||
allow: plugins?.allow,
|
||||
deny: plugins?.deny,
|
||||
slots: plugins?.slots,
|
||||
entries,
|
||||
},
|
||||
channels: channelPolicy,
|
||||
});
|
||||
}
|
||||
|
||||
// Single-slot Gateway-owned handoff. Replace or clear it at lifecycle boundaries;
|
||||
// never accumulate historical metadata snapshots here.
|
||||
export function setCurrentPluginMetadataSnapshot(
|
||||
@@ -40,6 +111,7 @@ export function setCurrentPluginMetadataSnapshot(
|
||||
workspaceDir?: string;
|
||||
} = {},
|
||||
): void {
|
||||
currentPluginMetadataConfigIdentityCache = new WeakMap();
|
||||
const compatiblePolicyHashes = snapshot
|
||||
? options.compatibleConfigs?.map((config) => resolveInstalledPluginIndexPolicyHash(config))
|
||||
: undefined;
|
||||
@@ -66,9 +138,52 @@ export function setCurrentPluginMetadataSnapshot(
|
||||
compatiblePolicyHashes,
|
||||
compatibleConfigFingerprints,
|
||||
);
|
||||
if (!snapshot) {
|
||||
return;
|
||||
}
|
||||
const env = options.env ?? process.env;
|
||||
const workspaceDir = options.workspaceDir ?? snapshot.workspaceDir;
|
||||
if (options.config) {
|
||||
const policyHash = resolveInstalledPluginIndexPolicyHash(options.config);
|
||||
const policyKey = resolveCurrentPluginMetadataPolicyKey(options.config);
|
||||
const discoveryKey = resolveCurrentPluginMetadataDiscoveryKey({
|
||||
config: options.config,
|
||||
env,
|
||||
workspaceDir,
|
||||
});
|
||||
currentPluginMetadataConfigIdentityCache.set(options.config, {
|
||||
policyHash,
|
||||
policyKey,
|
||||
discoveryKey,
|
||||
configFingerprint: resolvePluginMetadataControlPlaneFingerprint(options.config, {
|
||||
env,
|
||||
index: snapshot.index,
|
||||
policyHash,
|
||||
workspaceDir,
|
||||
}),
|
||||
});
|
||||
}
|
||||
for (const [index, config] of (options.compatibleConfigs ?? []).entries()) {
|
||||
const policyHash = compatiblePolicyHashes?.[index];
|
||||
const configFingerprint = compatibleConfigFingerprints?.[index];
|
||||
if (!policyHash || !configFingerprint) {
|
||||
continue;
|
||||
}
|
||||
currentPluginMetadataConfigIdentityCache.set(config, {
|
||||
policyHash,
|
||||
policyKey: resolveCurrentPluginMetadataPolicyKey(config),
|
||||
configFingerprint,
|
||||
discoveryKey: resolveCurrentPluginMetadataDiscoveryKey({
|
||||
config,
|
||||
env,
|
||||
workspaceDir,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function clearCurrentPluginMetadataSnapshot(): void {
|
||||
currentPluginMetadataConfigIdentityCache = new WeakMap();
|
||||
clearCurrentPluginMetadataSnapshotState();
|
||||
}
|
||||
|
||||
@@ -79,6 +194,7 @@ export function captureCurrentPluginMetadataSnapshotState(): CurrentPluginMetada
|
||||
export function restoreCurrentPluginMetadataSnapshotState(
|
||||
state: CurrentPluginMetadataSnapshotState,
|
||||
): void {
|
||||
currentPluginMetadataConfigIdentityCache = new WeakMap();
|
||||
setCurrentPluginMetadataSnapshotState(
|
||||
state.snapshot,
|
||||
state.configFingerprint,
|
||||
@@ -106,25 +222,47 @@ export function getCurrentPluginMetadataSnapshot(
|
||||
if (!snapshot) {
|
||||
return undefined;
|
||||
}
|
||||
const requestedPolicyHash = params.config
|
||||
? resolveInstalledPluginIndexPolicyHash(params.config)
|
||||
: undefined;
|
||||
const env = params.env ?? process.env;
|
||||
const requestedWorkspaceDir =
|
||||
params.workspaceDir ??
|
||||
(params.allowWorkspaceScopedSnapshot === true ? snapshot.workspaceDir : undefined);
|
||||
const cachedConfig = params.config && currentPluginMetadataConfigIdentityCache.get(params.config);
|
||||
const requestedPolicyKey =
|
||||
params.config && cachedConfig
|
||||
? resolveCurrentPluginMetadataPolicyKey(params.config)
|
||||
: undefined;
|
||||
const requestedDiscoveryKey =
|
||||
params.config && cachedConfig
|
||||
? resolveCurrentPluginMetadataDiscoveryKey({
|
||||
config: params.config,
|
||||
env,
|
||||
workspaceDir: requestedWorkspaceDir,
|
||||
})
|
||||
: undefined;
|
||||
const canReuseCachedConfig =
|
||||
cachedConfig !== undefined &&
|
||||
cachedConfig.policyKey === requestedPolicyKey &&
|
||||
cachedConfig.discoveryKey === requestedDiscoveryKey;
|
||||
const requestedPolicyHash = canReuseCachedConfig
|
||||
? cachedConfig.policyHash
|
||||
: params.config
|
||||
? resolveInstalledPluginIndexPolicyHash(params.config)
|
||||
: undefined;
|
||||
if (requestedPolicyHash && snapshot.policyHash !== requestedPolicyHash) {
|
||||
const compatiblePolicies = new Set(compatiblePolicyHashes ?? []);
|
||||
if (!compatiblePolicies.has(requestedPolicyHash)) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
const requestedWorkspaceDir =
|
||||
params.workspaceDir ??
|
||||
(params.allowWorkspaceScopedSnapshot === true ? snapshot.workspaceDir : undefined);
|
||||
if (params.config) {
|
||||
const requestedConfigFingerprint = resolvePluginMetadataControlPlaneFingerprint(params.config, {
|
||||
env: params.env,
|
||||
index: snapshot.index,
|
||||
policyHash: requestedPolicyHash,
|
||||
workspaceDir: requestedWorkspaceDir,
|
||||
});
|
||||
const requestedConfigFingerprint = canReuseCachedConfig
|
||||
? cachedConfig.configFingerprint
|
||||
: resolvePluginMetadataControlPlaneFingerprint(params.config, {
|
||||
env,
|
||||
index: snapshot.index,
|
||||
policyHash: requestedPolicyHash,
|
||||
workspaceDir: requestedWorkspaceDir,
|
||||
});
|
||||
const compatibleFingerprints = new Set(compatibleConfigFingerprints ?? []);
|
||||
const fingerprintMatches =
|
||||
configFingerprint === requestedConfigFingerprint ||
|
||||
|
||||
Reference in New Issue
Block a user