fix(plugins): exempt dreaming engine from memory slot fast-path in loader (#65411)

* fix(plugins): exempt dreaming engine from memory slot fast-path in loader

* fix(plugins): handle dreaming engine as slot + add tests for slot coexistence

* fix(plugins): narrow dreaming sidecar loading

* Update CHANGELOG.md

* Update CHANGELOG.md

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
pradeep7127
2026-04-12 19:41:25 +04:00
committed by GitHub
parent 559de69488
commit 5fde14b844
3 changed files with 257 additions and 52 deletions

View File

@@ -14,7 +14,15 @@ import type { PluginInstallRecord } from "../config/types.plugins.js";
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
DEFAULT_MEMORY_DREAMING_PLUGIN_ID,
resolveMemoryDreamingConfig,
resolveMemoryDreamingPluginConfig,
} from "../memory-host-sdk/dreaming.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../shared/string-coerce.js";
import { resolveUserPath } from "../utils.js";
import { buildPluginApi } from "./api-builder.js";
import { inspectBundleMcpRuntimeSupport } from "./bundle-mcp.js";
@@ -127,6 +135,25 @@ const CLI_METADATA_ENTRY_BASENAMES = [
"cli-metadata.cjs",
] as const;
function resolveDreamingSidecarEngineId(params: {
cfg: OpenClawConfig;
memorySlot: string | null | undefined;
}): string | null {
const normalizedMemorySlot = normalizeLowercaseStringOrEmpty(params.memorySlot);
if (
!normalizedMemorySlot ||
normalizedMemorySlot === "none" ||
normalizedMemorySlot === DEFAULT_MEMORY_DREAMING_PLUGIN_ID
) {
return null;
}
const dreamingConfig = resolveMemoryDreamingConfig({
pluginConfig: resolveMemoryDreamingPluginConfig(params.cfg),
cfg: params.cfg,
});
return dreamingConfig.enabled ? DEFAULT_MEMORY_DREAMING_PLUGIN_ID : null;
}
export class PluginLoadFailureError extends Error {
readonly pluginIds: string[];
readonly registry: PluginRegistry;
@@ -1286,6 +1313,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
const memorySlot = normalized.slots.memory;
let selectedMemoryPluginId: string | null = null;
let memorySlotMatched = false;
const dreamingEngineId = resolveDreamingSidecarEngineId({ cfg, memorySlot });
for (const candidate of orderedCandidates) {
const manifestRecord = manifestByRoot.get(candidate.rootDir);
@@ -1477,25 +1505,29 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
}
// Fast-path bundled memory plugins that are guaranteed disabled by slot policy.
// This avoids opening/importing heavy memory plugin modules that will never register.
// 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" &&
candidate.origin === "bundled" &&
hasKind(manifestRecord.kind, "memory")
) {
const earlyMemoryDecision = resolveMemorySlotDecision({
id: record.id,
kind: manifestRecord.kind,
slot: memorySlot,
selectedId: selectedMemoryPluginId,
});
if (!earlyMemoryDecision.enabled) {
record.enabled = false;
record.status = "disabled";
record.error = earlyMemoryDecision.reason;
markPluginActivationDisabled(record, earlyMemoryDecision.reason);
registry.plugins.push(record);
seenIds.set(pluginId, candidate.origin);
continue;
if (pluginId !== dreamingEngineId) {
const earlyMemoryDecision = resolveMemorySlotDecision({
id: record.id,
kind: manifestRecord.kind,
slot: memorySlot,
selectedId: selectedMemoryPluginId,
});
if (!earlyMemoryDecision.enabled) {
record.enabled = false;
record.status = "disabled";
record.error = earlyMemoryDecision.reason;
markPluginActivationDisabled(record, earlyMemoryDecision.reason);
registry.plugins.push(record);
seenIds.set(pluginId, candidate.origin);
continue;
}
}
}
@@ -1512,7 +1544,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
selectedId: selectedMemoryPluginId,
});
if (!memoryDecision.enabled) {
if (!memoryDecision.enabled && pluginId !== dreamingEngineId) {
record.enabled = false;
record.status = "disabled";
record.error = memoryDecision.reason;
@@ -1653,26 +1685,28 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
}
if (registrationMode === "full") {
const memoryDecision = resolveMemorySlotDecision({
id: record.id,
kind: record.kind,
slot: memorySlot,
selectedId: selectedMemoryPluginId,
});
if (pluginId !== dreamingEngineId) {
const memoryDecision = resolveMemorySlotDecision({
id: record.id,
kind: record.kind,
slot: memorySlot,
selectedId: selectedMemoryPluginId,
});
if (!memoryDecision.enabled) {
record.enabled = false;
record.status = "disabled";
record.error = memoryDecision.reason;
markPluginActivationDisabled(record, memoryDecision.reason);
registry.plugins.push(record);
seenIds.set(pluginId, candidate.origin);
continue;
}
if (!memoryDecision.enabled) {
record.enabled = false;
record.status = "disabled";
record.error = memoryDecision.reason;
markPluginActivationDisabled(record, memoryDecision.reason);
registry.plugins.push(record);
seenIds.set(pluginId, candidate.origin);
continue;
}
if (memoryDecision.selected && hasKind(record.kind, "memory")) {
selectedMemoryPluginId = record.id;
record.memorySlotSelected = true;
if (memoryDecision.selected && hasKind(record.kind, "memory")) {
selectedMemoryPluginId = record.id;
record.memorySlotSelected = true;
}
}
}
@@ -1889,6 +1923,7 @@ export async function loadOpenClawPluginCliRegistry(
const seenIds = new Map<string, PluginRecord["origin"]>();
const memorySlot = normalized.slots.memory;
let selectedMemoryPluginId: string | null = null;
const dreamingEngineId = resolveDreamingSidecarEngineId({ cfg, memorySlot });
for (const candidate of orderedCandidates) {
const manifestRecord = manifestByRoot.get(candidate.rootDir);
@@ -2089,24 +2124,26 @@ export async function loadOpenClawPluginCliRegistry(
}
record.kind = definition?.kind ?? record.kind;
const memoryDecision = resolveMemorySlotDecision({
id: record.id,
kind: record.kind,
slot: memorySlot,
selectedId: selectedMemoryPluginId,
});
if (!memoryDecision.enabled) {
record.enabled = false;
record.status = "disabled";
record.error = memoryDecision.reason;
markPluginActivationDisabled(record, memoryDecision.reason);
registry.plugins.push(record);
seenIds.set(pluginId, candidate.origin);
continue;
}
if (memoryDecision.selected && hasKind(record.kind, "memory")) {
selectedMemoryPluginId = record.id;
record.memorySlotSelected = true;
if (pluginId !== dreamingEngineId) {
const memoryDecision = resolveMemorySlotDecision({
id: record.id,
kind: record.kind,
slot: memorySlot,
selectedId: selectedMemoryPluginId,
});
if (!memoryDecision.enabled) {
record.enabled = false;
record.status = "disabled";
record.error = memoryDecision.reason;
markPluginActivationDisabled(record, memoryDecision.reason);
registry.plugins.push(record);
seenIds.set(pluginId, candidate.origin);
continue;
}
if (memoryDecision.selected && hasKind(record.kind, "memory")) {
selectedMemoryPluginId = record.id;
record.memorySlotSelected = true;
}
}
if (typeof register !== "function") {