mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:10:42 +00:00
fix(plugins): cache discovery registration snapshots
Co-authored-by: junpei.o <14040213+livingghost@users.noreply.github.com> Co-authored-by: Yoshiaki Okuyama <okuyam2y@gmail.com> Co-authored-by: Shion Eria <shioneria@foxmail.com> Co-authored-by: Billy Shih <1472300+bbshih@users.noreply.github.com>
This commit is contained in:
@@ -66,7 +66,7 @@ import {
|
||||
restorePluginInteractiveHandlers,
|
||||
} from "./interactive-registry.js";
|
||||
import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "./jiti-loader-cache.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import { loadPluginManifestRegistry, type PluginManifestRecord } from "./manifest-registry.js";
|
||||
import type { PluginBundleFormat, PluginDiagnostic, PluginFormat } from "./manifest-types.js";
|
||||
import type { PluginManifestContracts } from "./manifest.js";
|
||||
import {
|
||||
@@ -124,6 +124,7 @@ import type {
|
||||
OpenClawPluginDefinition,
|
||||
OpenClawPluginModule,
|
||||
PluginLogger,
|
||||
PluginRegistrationMode,
|
||||
} from "./types.js";
|
||||
|
||||
export type PluginLoadResult = PluginRegistry;
|
||||
@@ -808,6 +809,7 @@ function buildCacheKey(params: {
|
||||
runtimeSubagentMode?: "default" | "explicit" | "gateway-bindable";
|
||||
pluginSdkResolution?: PluginSdkResolutionPreference;
|
||||
coreGatewayMethodNames?: string[];
|
||||
activate?: boolean;
|
||||
}): string {
|
||||
const { roots, loadPaths } = resolvePluginCacheInputs({
|
||||
workspaceDir: params.workspaceDir,
|
||||
@@ -845,12 +847,13 @@ function buildCacheKey(params: {
|
||||
params.installBundledRuntimeDeps === false ? "skip-runtime-deps" : "install-runtime-deps";
|
||||
const runtimeSubagentMode = params.runtimeSubagentMode ?? "default";
|
||||
const gatewayMethodsKey = JSON.stringify(params.coreGatewayMethodNames ?? []);
|
||||
const activationMode = params.activate === false ? "snapshot" : "active";
|
||||
return `${roots.workspace ?? ""}::${roots.global ?? ""}::${roots.stock ?? ""}::${JSON.stringify({
|
||||
...params.plugins,
|
||||
installs,
|
||||
loadPaths,
|
||||
activationMetadataKey: params.activationMetadataKey ?? "",
|
||||
})}::${scopeKey}::${setupOnlyKey}::${setupOnlyModeKey}::${setupOnlyRequirementKey}::${startupChannelMode}::${moduleLoadMode}::${bundledRuntimeDepsMode}::${runtimeSubagentMode}::${params.pluginSdkResolution ?? "auto"}::${gatewayMethodsKey}`;
|
||||
})}::${scopeKey}::${setupOnlyKey}::${setupOnlyModeKey}::${setupOnlyRequirementKey}::${startupChannelMode}::${moduleLoadMode}::${bundledRuntimeDepsMode}::${runtimeSubagentMode}::${params.pluginSdkResolution ?? "auto"}::${gatewayMethodsKey}::${activationMode}`;
|
||||
}
|
||||
|
||||
function matchesScopedPluginRequest(params: {
|
||||
@@ -933,6 +936,87 @@ function hasExplicitCompatibilityInputs(options: PluginLoadOptions): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
type PluginRegistrationPlan = {
|
||||
/** Public compatibility label passed to plugin register(api). */
|
||||
mode: PluginRegistrationMode;
|
||||
/** Load a setup entry instead of the normal runtime entry. */
|
||||
loadSetupEntry: boolean;
|
||||
/** Setup flow also needs the runtime channel entry for runtime setters/plugin shape. */
|
||||
loadSetupRuntimeEntry: boolean;
|
||||
/** Apply runtime capability policy such as memory-slot selection. */
|
||||
runRuntimeCapabilityPolicy: boolean;
|
||||
/** Register metadata that only belongs to live activation, not discovery snapshots. */
|
||||
runFullActivationOnlyRegistrations: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert loader intent into explicit behavior flags.
|
||||
*
|
||||
* Registration modes are plugin-facing labels; this plan is the internal source
|
||||
* of truth for which entrypoint to load and which activation-only policies run.
|
||||
*/
|
||||
function resolvePluginRegistrationPlan(params: {
|
||||
canLoadScopedSetupOnlyChannelPlugin: boolean;
|
||||
scopedSetupOnlyChannelPluginRequested: boolean;
|
||||
requireSetupEntryForSetupOnlyChannelPlugins: boolean;
|
||||
enableStateEnabled: boolean;
|
||||
shouldLoadModules: boolean;
|
||||
validateOnly: boolean;
|
||||
shouldActivate: boolean;
|
||||
manifestRecord: PluginManifestRecord;
|
||||
cfg: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
preferSetupRuntimeForChannelPlugins: boolean;
|
||||
}): PluginRegistrationPlan | null {
|
||||
if (params.canLoadScopedSetupOnlyChannelPlugin) {
|
||||
return {
|
||||
mode: "setup-only",
|
||||
loadSetupEntry: true,
|
||||
loadSetupRuntimeEntry: false,
|
||||
runRuntimeCapabilityPolicy: false,
|
||||
runFullActivationOnlyRegistrations: false,
|
||||
};
|
||||
}
|
||||
if (
|
||||
params.scopedSetupOnlyChannelPluginRequested &&
|
||||
params.requireSetupEntryForSetupOnlyChannelPlugins
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (!params.enableStateEnabled) {
|
||||
return null;
|
||||
}
|
||||
const loadSetupRuntimeEntry =
|
||||
params.shouldLoadModules &&
|
||||
!params.validateOnly &&
|
||||
shouldLoadChannelPluginInSetupRuntime({
|
||||
manifestChannels: params.manifestRecord.channels,
|
||||
setupSource: params.manifestRecord.setupSource,
|
||||
startupDeferConfiguredChannelFullLoadUntilAfterListen:
|
||||
params.manifestRecord.startupDeferConfiguredChannelFullLoadUntilAfterListen,
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
preferSetupRuntimeForChannelPlugins: params.preferSetupRuntimeForChannelPlugins,
|
||||
});
|
||||
if (loadSetupRuntimeEntry) {
|
||||
return {
|
||||
mode: "setup-runtime",
|
||||
loadSetupEntry: true,
|
||||
loadSetupRuntimeEntry: true,
|
||||
runRuntimeCapabilityPolicy: false,
|
||||
runFullActivationOnlyRegistrations: false,
|
||||
};
|
||||
}
|
||||
const mode = params.shouldActivate ? "full" : "discovery";
|
||||
return {
|
||||
mode,
|
||||
loadSetupEntry: false,
|
||||
loadSetupRuntimeEntry: false,
|
||||
runRuntimeCapabilityPolicy: true,
|
||||
runFullActivationOnlyRegistrations: mode === "full",
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
||||
const env = options.env ?? process.env;
|
||||
const cfg = applyTestPluginDefaults(options.config ?? {}, env);
|
||||
@@ -976,6 +1060,7 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
|
||||
runtimeSubagentMode,
|
||||
pluginSdkResolution: options.pluginSdkResolution,
|
||||
coreGatewayMethodNames,
|
||||
activate: options.activate,
|
||||
});
|
||||
return {
|
||||
env,
|
||||
@@ -1053,6 +1138,15 @@ function getCompatibleActivePluginRegistry(
|
||||
if (loadContext.cacheKey === activeCacheKey) {
|
||||
return activeRegistry;
|
||||
}
|
||||
if (!loadContext.shouldActivate) {
|
||||
const activatingCacheKey = resolvePluginLoadCacheContext({
|
||||
...options,
|
||||
activate: true,
|
||||
}).cacheKey;
|
||||
if (activatingCacheKey === activeCacheKey) {
|
||||
return activeRegistry;
|
||||
}
|
||||
}
|
||||
if (
|
||||
loadContext.runtimeSubagentMode === "default" &&
|
||||
getActivePluginRuntimeSubagentMode() === "gateway-bindable"
|
||||
@@ -1067,6 +1161,19 @@ function getCompatibleActivePluginRegistry(
|
||||
if (gatewayBindableCacheKey === activeCacheKey) {
|
||||
return activeRegistry;
|
||||
}
|
||||
if (!loadContext.shouldActivate) {
|
||||
const activatingGatewayBindableCacheKey = resolvePluginLoadCacheContext({
|
||||
...options,
|
||||
activate: true,
|
||||
runtimeOptions: {
|
||||
...options.runtimeOptions,
|
||||
allowGatewaySubagentBinding: true,
|
||||
},
|
||||
}).cacheKey;
|
||||
if (activatingGatewayBindableCacheKey === activeCacheKey) {
|
||||
return activeRegistry;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -1851,13 +1958,6 @@ function activatePluginRegistry(
|
||||
}
|
||||
|
||||
export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegistry {
|
||||
// Snapshot (non-activating) loads must disable the cache to avoid storing a registry
|
||||
// whose commands were never globally registered.
|
||||
if (options.activate === false && options.cache !== false) {
|
||||
throw new Error(
|
||||
"loadOpenClawPlugins: activate:false requires cache:false to prevent command registry divergence",
|
||||
);
|
||||
}
|
||||
const {
|
||||
env,
|
||||
cfg,
|
||||
@@ -1882,21 +1982,21 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
if (cacheEnabled) {
|
||||
const cached = getCachedPluginRegistry(cacheKey);
|
||||
if (cached) {
|
||||
restoreRegisteredAgentHarnesses(cached.agentHarnesses);
|
||||
restorePluginCommands(cached.commands ?? []);
|
||||
restoreRegisteredCompactionProviders(cached.compactionProviders);
|
||||
restoreDetachedTaskLifecycleRuntimeRegistration(cached.detachedTaskRuntimeRegistration);
|
||||
restorePluginInteractiveHandlers(cached.interactiveHandlers ?? []);
|
||||
restoreRegisteredMemoryEmbeddingProviders(cached.memoryEmbeddingProviders);
|
||||
restoreMemoryPluginState({
|
||||
capability: cached.memoryCapability,
|
||||
corpusSupplements: cached.memoryCorpusSupplements,
|
||||
promptBuilder: cached.memoryPromptBuilder,
|
||||
promptSupplements: cached.memoryPromptSupplements,
|
||||
flushPlanResolver: cached.memoryFlushPlanResolver,
|
||||
runtime: cached.memoryRuntime,
|
||||
});
|
||||
if (shouldActivate) {
|
||||
restoreRegisteredAgentHarnesses(cached.agentHarnesses);
|
||||
restorePluginCommands(cached.commands ?? []);
|
||||
restoreRegisteredCompactionProviders(cached.compactionProviders);
|
||||
restoreDetachedTaskLifecycleRuntimeRegistration(cached.detachedTaskRuntimeRegistration);
|
||||
restorePluginInteractiveHandlers(cached.interactiveHandlers ?? []);
|
||||
restoreRegisteredMemoryEmbeddingProviders(cached.memoryEmbeddingProviders);
|
||||
restoreMemoryPluginState({
|
||||
capability: cached.memoryCapability,
|
||||
corpusSupplements: cached.memoryCorpusSupplements,
|
||||
promptBuilder: cached.memoryPromptBuilder,
|
||||
promptSupplements: cached.memoryPromptSupplements,
|
||||
flushPlanResolver: cached.memoryFlushPlanResolver,
|
||||
runtime: cached.memoryRuntime,
|
||||
});
|
||||
activatePluginRegistry(
|
||||
cached.registry,
|
||||
cacheKey,
|
||||
@@ -2178,33 +2278,27 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
const scopedSetupOnlyChannelPluginRequested =
|
||||
includeSetupOnlyChannelPlugins &&
|
||||
!validateOnly &&
|
||||
onlyPluginIdSet &&
|
||||
Boolean(onlyPluginIdSet) &&
|
||||
manifestRecord.channels.length > 0 &&
|
||||
(!enableState.enabled || forceSetupOnlyChannelPlugins);
|
||||
const canLoadScopedSetupOnlyChannelPlugin =
|
||||
scopedSetupOnlyChannelPluginRequested &&
|
||||
(!requireSetupEntryForSetupOnlyChannelPlugins || Boolean(manifestRecord.setupSource));
|
||||
const registrationMode = canLoadScopedSetupOnlyChannelPlugin
|
||||
? "setup-only"
|
||||
: scopedSetupOnlyChannelPluginRequested && requireSetupEntryForSetupOnlyChannelPlugins
|
||||
? null
|
||||
: enableState.enabled
|
||||
? shouldLoadModules &&
|
||||
!validateOnly &&
|
||||
shouldLoadChannelPluginInSetupRuntime({
|
||||
manifestChannels: manifestRecord.channels,
|
||||
setupSource: manifestRecord.setupSource,
|
||||
startupDeferConfiguredChannelFullLoadUntilAfterListen:
|
||||
manifestRecord.startupDeferConfiguredChannelFullLoadUntilAfterListen,
|
||||
cfg,
|
||||
env,
|
||||
preferSetupRuntimeForChannelPlugins,
|
||||
})
|
||||
? "setup-runtime"
|
||||
: "full"
|
||||
: null;
|
||||
const registrationPlan = resolvePluginRegistrationPlan({
|
||||
canLoadScopedSetupOnlyChannelPlugin,
|
||||
scopedSetupOnlyChannelPluginRequested,
|
||||
requireSetupEntryForSetupOnlyChannelPlugins,
|
||||
enableStateEnabled: enableState.enabled,
|
||||
shouldLoadModules,
|
||||
validateOnly,
|
||||
shouldActivate,
|
||||
manifestRecord,
|
||||
cfg,
|
||||
env,
|
||||
preferSetupRuntimeForChannelPlugins,
|
||||
});
|
||||
|
||||
if (!registrationMode) {
|
||||
if (!registrationPlan) {
|
||||
record.status = "disabled";
|
||||
record.error = enableState.reason;
|
||||
markPluginActivationDisabled(record, enableState.reason);
|
||||
@@ -2212,6 +2306,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
seenIds.set(pluginId, candidate.origin);
|
||||
continue;
|
||||
}
|
||||
const registrationMode = registrationPlan.mode;
|
||||
if (!enableState.enabled) {
|
||||
record.status = "disabled";
|
||||
record.error = enableState.reason;
|
||||
@@ -2340,7 +2435,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
// Exception: the dreaming engine (memory-core by default) must load alongside the
|
||||
// selected memory slot plugin so dreaming can run even when lancedb holds the slot.
|
||||
if (
|
||||
registrationMode === "full" &&
|
||||
registrationPlan.runRuntimeCapabilityPolicy &&
|
||||
candidate.origin === "bundled" &&
|
||||
hasKind(manifestRecord.kind, "memory")
|
||||
) {
|
||||
@@ -2368,7 +2463,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!shouldLoadModules && registrationMode === "full") {
|
||||
if (!shouldLoadModules && registrationPlan.runRuntimeCapabilityPolicy) {
|
||||
const memoryDecision = resolveMemorySlotDecision({
|
||||
id: record.id,
|
||||
kind: record.kind,
|
||||
@@ -2414,8 +2509,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
}
|
||||
|
||||
const loadSource =
|
||||
(registrationMode === "setup-only" || registrationMode === "setup-runtime") &&
|
||||
runtimeSetupSource
|
||||
registrationPlan.loadSetupEntry && runtimeSetupSource
|
||||
? runtimeSetupSource
|
||||
: runtimeCandidateSource;
|
||||
const moduleLoadSource = resolveCanonicalDistRuntimeSource(loadSource);
|
||||
@@ -2461,10 +2555,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
(registrationMode === "setup-only" || registrationMode === "setup-runtime") &&
|
||||
manifestRecord.setupSource
|
||||
) {
|
||||
if (registrationPlan.loadSetupEntry && manifestRecord.setupSource) {
|
||||
const setupRegistration = resolveSetupChannelRegistration(mod, {
|
||||
installRuntimeDeps:
|
||||
shouldInstallBundledRuntimeDeps &&
|
||||
@@ -2507,7 +2598,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
let mergedSetupRegistration = setupRegistration;
|
||||
let runtimeSetterApplied = false;
|
||||
if (
|
||||
registrationMode === "setup-runtime" &&
|
||||
registrationPlan.loadSetupRuntimeEntry &&
|
||||
setupRegistration.usesBundledSetupContract &&
|
||||
runtimeCandidateSource !== safeSource
|
||||
) {
|
||||
@@ -2685,7 +2776,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
memorySlotMatched = true;
|
||||
}
|
||||
|
||||
if (registrationMode === "full") {
|
||||
if (registrationPlan.runRuntimeCapabilityPolicy) {
|
||||
if (pluginId !== dreamingEngineId) {
|
||||
const memoryDecision = resolveMemorySlotDecision({
|
||||
id: record.id,
|
||||
@@ -2711,7 +2802,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
}
|
||||
}
|
||||
|
||||
if (registrationMode === "full") {
|
||||
if (registrationPlan.runFullActivationOnlyRegistrations) {
|
||||
if (definition?.reload) {
|
||||
registerReload(record, definition.reload);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user