Files
openclaw/src/plugins/current-plugin-metadata-snapshot.ts
WhatsSkiLL 22b8e1cf4f fix(plugins): scope startup metadata manifest reads
Limit plugin metadata snapshots to the channel, provider, and startup surfaces that need them, while preserving unscoped fallback for incomplete index data and provider runtime resolution.

Refs #70533.
Refs #84628.

Co-authored-by: IWhatsskill <IWhatsskill@users.noreply.github.com>
2026-05-31 11:58:56 +01:00

263 lines
9.1 KiB
TypeScript

import { setCurrentManifestModelIdNormalizationRecords } from "@openclaw/model-catalog-core/provider-model-id-normalization";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import {
clearCurrentPluginMetadataSnapshotState,
getCurrentPluginMetadataSnapshotState,
setCurrentPluginMetadataSnapshotState,
} from "./current-plugin-metadata-state.js";
import { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js";
import {
resolvePluginControlPlaneFingerprint,
type ResolvePluginControlPlaneContextParams,
} from "./plugin-control-plane-context.js";
import { registerPluginMetadataProcessMemoLifecycleClear } from "./plugin-metadata-lifecycle.js";
import type {
PluginMetadataSnapshot,
PluginMetadataSnapshotPluginIdScope,
} from "./plugin-metadata-snapshot.types.js";
import { normalizePluginIdScope, serializePluginIdScope } from "./plugin-scope.js";
type CurrentPluginMetadataSnapshotState = ReturnType<typeof getCurrentPluginMetadataSnapshotState>;
let currentPluginMetadataConfigIdentityCache = new WeakSet<OpenClawConfig>();
registerPluginMetadataProcessMemoLifecycleClear(() => {
setCurrentManifestModelIdNormalizationRecords(undefined);
});
export function resolvePluginMetadataControlPlaneFingerprint(
config?: OpenClawConfig,
options: Omit<ResolvePluginControlPlaneContextParams, "config"> = {},
): string {
return resolvePluginControlPlaneFingerprint({
config,
...options,
});
}
export function isReusableCurrentPluginMetadataSnapshot(
_snapshot: PluginMetadataSnapshot,
): boolean {
return true;
}
// Single-slot Gateway-owned handoff. Replace or clear it at lifecycle boundaries;
// never accumulate historical metadata snapshots here.
export function setCurrentPluginMetadataSnapshot(
snapshot: PluginMetadataSnapshot | undefined,
options: {
config?: OpenClawConfig;
compatibleConfigs?: readonly OpenClawConfig[];
env?: NodeJS.ProcessEnv;
workspaceDir?: string;
} = {},
): void {
currentPluginMetadataConfigIdentityCache = new WeakSet();
const compatiblePolicyHashes = snapshot
? options.compatibleConfigs?.map((config) => resolveInstalledPluginIndexPolicyHash(config))
: undefined;
const compatibleConfigFingerprints = snapshot
? options.compatibleConfigs?.map((config, index) =>
resolvePluginMetadataControlPlaneFingerprint(config, {
env: options.env,
index: snapshot.index,
policyHash: compatiblePolicyHashes?.[index],
workspaceDir: options.workspaceDir ?? snapshot.workspaceDir,
}),
)
: undefined;
const configFingerprint = snapshot
? resolvePluginMetadataControlPlaneFingerprint(options.config, {
env: options.env,
index: snapshot.index,
policyHash: snapshot.policyHash,
workspaceDir: options.workspaceDir ?? snapshot.workspaceDir,
})
: undefined;
const defaultDiscoveryConfigFingerprint = snapshot
? resolvePluginMetadataControlPlaneFingerprint(
{},
{
env: options.env,
index: snapshot.index,
policyHash: snapshot.policyHash,
workspaceDir: options.workspaceDir ?? snapshot.workspaceDir,
},
)
: undefined;
const defaultDiscoveryCompatible =
snapshot &&
defaultDiscoveryConfigFingerprint &&
(configFingerprint === defaultDiscoveryConfigFingerprint ||
snapshot.configFingerprint === defaultDiscoveryConfigFingerprint ||
Boolean(compatibleConfigFingerprints?.includes(defaultDiscoveryConfigFingerprint)));
setCurrentManifestModelIdNormalizationRecords(
defaultDiscoveryCompatible ? snapshot.plugins : undefined,
);
setCurrentPluginMetadataSnapshotState(
snapshot,
configFingerprint,
compatiblePolicyHashes,
compatibleConfigFingerprints,
);
if (!snapshot) {
return;
}
if (options.config) {
const policyHash = resolveInstalledPluginIndexPolicyHash(options.config);
if (
policyHash === snapshot.policyHash ||
Boolean(compatiblePolicyHashes?.includes(policyHash))
) {
currentPluginMetadataConfigIdentityCache.add(options.config);
}
}
for (const config of options.compatibleConfigs ?? []) {
currentPluginMetadataConfigIdentityCache.add(config);
}
}
export function clearCurrentPluginMetadataSnapshot(): void {
currentPluginMetadataConfigIdentityCache = new WeakSet();
setCurrentManifestModelIdNormalizationRecords(undefined);
clearCurrentPluginMetadataSnapshotState();
}
export function captureCurrentPluginMetadataSnapshotState(): CurrentPluginMetadataSnapshotState {
return getCurrentPluginMetadataSnapshotState();
}
export function restoreCurrentPluginMetadataSnapshotState(
state: CurrentPluginMetadataSnapshotState,
): void {
currentPluginMetadataConfigIdentityCache = new WeakSet();
const snapshot = state.snapshot as PluginMetadataSnapshot | undefined;
const defaultDiscoveryConfigFingerprint = snapshot
? resolvePluginMetadataControlPlaneFingerprint(
{},
{
index: snapshot.index,
policyHash: snapshot.policyHash,
workspaceDir: snapshot.workspaceDir,
},
)
: undefined;
const defaultDiscoveryCompatible =
snapshot &&
defaultDiscoveryConfigFingerprint &&
(state.configFingerprint === defaultDiscoveryConfigFingerprint ||
snapshot.configFingerprint === defaultDiscoveryConfigFingerprint ||
Boolean(state.compatibleConfigFingerprints?.includes(defaultDiscoveryConfigFingerprint)));
setCurrentManifestModelIdNormalizationRecords(
defaultDiscoveryCompatible ? snapshot.plugins : undefined,
);
setCurrentPluginMetadataSnapshotState(
state.snapshot,
state.configFingerprint,
state.compatiblePolicyHashes,
state.compatibleConfigFingerprints,
);
}
export function getCurrentPluginMetadataSnapshot(
params: {
config?: OpenClawConfig;
env?: NodeJS.ProcessEnv;
allowScopedSnapshot?: boolean;
pluginIds?: readonly string[];
pluginIdScope?: PluginMetadataSnapshotPluginIdScope;
workspaceDir?: string;
allowWorkspaceScopedSnapshot?: boolean;
requireDefaultDiscoveryContext?: boolean;
} = {},
): PluginMetadataSnapshot | undefined {
const {
snapshot: rawSnapshot,
configFingerprint,
compatiblePolicyHashes,
compatibleConfigFingerprints,
} = getCurrentPluginMetadataSnapshotState();
const snapshot = rawSnapshot as PluginMetadataSnapshot | undefined;
if (!snapshot) {
return undefined;
}
const env = params.env ?? process.env;
const requestedPluginIds = normalizePluginIdScope(
params.pluginIds ?? params.pluginIdScope?.resolve({ index: snapshot.index }),
);
const snapshotPluginIds = normalizePluginIdScope(snapshot.pluginIds);
if (
requestedPluginIds !== undefined &&
serializePluginIdScope(snapshotPluginIds) !== serializePluginIdScope(requestedPluginIds)
) {
return undefined;
}
if (
snapshotPluginIds !== undefined &&
requestedPluginIds === undefined &&
params.allowScopedSnapshot !== true
) {
return undefined;
}
const requestedWorkspaceDir =
params.workspaceDir ??
(params.allowWorkspaceScopedSnapshot === true ? snapshot.workspaceDir : undefined);
if (snapshot.workspaceDir !== undefined && requestedWorkspaceDir === undefined) {
return undefined;
}
if (
requestedWorkspaceDir !== undefined &&
(snapshot.workspaceDir ?? "") !== (requestedWorkspaceDir ?? "")
) {
return undefined;
}
const canReuseCachedConfig = Boolean(
params.config && currentPluginMetadataConfigIdentityCache.has(params.config),
);
if (canReuseCachedConfig && params.requireDefaultDiscoveryContext !== true) {
return snapshot;
}
const requestedPolicyHash =
params.config && !canReuseCachedConfig
? resolveInstalledPluginIndexPolicyHash(params.config)
: undefined;
if (requestedPolicyHash && snapshot.policyHash !== requestedPolicyHash) {
if (!compatiblePolicyHashes?.includes(requestedPolicyHash)) {
return undefined;
}
}
if (params.config && !canReuseCachedConfig) {
const requestedConfigFingerprint = resolvePluginMetadataControlPlaneFingerprint(params.config, {
env,
index: snapshot.index,
policyHash: requestedPolicyHash,
workspaceDir: requestedWorkspaceDir,
});
const fingerprintMatches =
configFingerprint === requestedConfigFingerprint ||
snapshot.configFingerprint === requestedConfigFingerprint ||
Boolean(compatibleConfigFingerprints?.includes(requestedConfigFingerprint));
if (!fingerprintMatches) {
return undefined;
}
}
if (params.requireDefaultDiscoveryContext === true) {
const defaultDiscoveryConfigFingerprint = resolvePluginMetadataControlPlaneFingerprint(
{},
{
env: params.env,
index: snapshot.index,
policyHash: snapshot.policyHash,
workspaceDir: requestedWorkspaceDir,
},
);
const fingerprintMatches =
configFingerprint === defaultDiscoveryConfigFingerprint ||
snapshot.configFingerprint === defaultDiscoveryConfigFingerprint ||
Boolean(compatibleConfigFingerprints?.includes(defaultDiscoveryConfigFingerprint));
if (!fingerprintMatches) {
return undefined;
}
}
return snapshot;
}