mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
fix: include memory plugins in gateway startup (openclaw#64423)
Verified: - pnpm build - pnpm check - pnpm test -- src/plugins/channel-plugin-ids.test.ts Co-authored-by: EronFan <50734013+EronFan@users.noreply.github.com>
This commit is contained in:
@@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Security/nodes: keep `nodes` tool output paths inside the workspace boundary so model-driven node writes cannot escape the intended workspace. (#63551) Thanks @pgondhi987.
|
||||
- Security/QQBot: enforce media storage boundaries for all outbound local file paths and route image-size probes through SSRF-guarded media fetching instead of raw `fetch()`. (#63271, #63495) Thanks @pgondhi987.
|
||||
- Channel setup: ignore workspace plugin shadows when resolving trusted channel setup catalog entries so onboarding and setup flows keep using the bundled, trusted setup contract.
|
||||
- Gateway/memory startup: load the explicitly selected memory-slot plugin during gateway startup, while keeping restrictive allowlists and implicit default memory slots from auto-starting unrelated memory plugins. (#64423) Thanks @EronFan.
|
||||
- Config/plugins: let config writes keep disabled plugin entries without forcing required plugin config schemas or crashing raw plugin validation, and avoid re-activating plugin registry state during schema checks. (#54971, #63296) Thanks @fuller-stack-dev.
|
||||
- Config validation: surface the actual offending field for strict-schema union failures in bindings, including top-level unexpected keys on the matching ACP branch. (#40841) Thanks @Hollychou924.
|
||||
- Wizard/plugin config: coerce integer-typed plugin config fields from interactive text input so integer schema values persist as numbers instead of failing validation. (#63346) Thanks @jalehman.
|
||||
|
||||
@@ -49,6 +49,14 @@ function createManifestRegistryFixture() {
|
||||
providers: ["demo-provider"],
|
||||
cliBackends: ["demo-cli"],
|
||||
},
|
||||
{
|
||||
id: "voice-call",
|
||||
channels: [],
|
||||
origin: "bundled",
|
||||
enabledByDefault: undefined,
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
},
|
||||
{
|
||||
id: "memory-core",
|
||||
kind: "memory",
|
||||
@@ -67,14 +75,6 @@ function createManifestRegistryFixture() {
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
},
|
||||
{
|
||||
id: "voice-call",
|
||||
channels: [],
|
||||
origin: "bundled",
|
||||
enabledByDefault: undefined,
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
},
|
||||
{
|
||||
id: "demo-global-sidecar",
|
||||
channels: [],
|
||||
@@ -121,6 +121,7 @@ function createStartupConfig(params: {
|
||||
channelIds?: string[];
|
||||
allowPluginIds?: string[];
|
||||
noConfiguredChannels?: boolean;
|
||||
memorySlot?: string;
|
||||
}) {
|
||||
return {
|
||||
...(params.noConfiguredChannels
|
||||
@@ -138,6 +139,7 @@ function createStartupConfig(params: {
|
||||
? {
|
||||
plugins: {
|
||||
...(params.allowPluginIds?.length ? { allow: params.allowPluginIds } : {}),
|
||||
...(params.memorySlot ? { slots: { memory: params.memorySlot } } : {}),
|
||||
entries: Object.fromEntries(
|
||||
params.enabledPluginIds.map((pluginId) => [pluginId, { enabled: true }]),
|
||||
),
|
||||
@@ -149,6 +151,14 @@ function createStartupConfig(params: {
|
||||
allow: params.allowPluginIds,
|
||||
},
|
||||
}
|
||||
: params.memorySlot
|
||||
? {
|
||||
plugins: {
|
||||
slots: {
|
||||
memory: params.memorySlot,
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(params.providerIds?.length
|
||||
? {
|
||||
@@ -250,6 +260,9 @@ describe("resolveGatewayStartupPluginIds", () => {
|
||||
"voice-call": {
|
||||
enabled: true,
|
||||
},
|
||||
"memory-core": {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
@@ -261,74 +274,32 @@ describe("resolveGatewayStartupPluginIds", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("includes memory-core at startup when dreaming is enabled", () => {
|
||||
it("includes the explicitly selected memory slot plugin in startup scope", () => {
|
||||
expectStartupPluginIdsCase({
|
||||
config: {
|
||||
channels: {},
|
||||
plugins: {
|
||||
entries: {
|
||||
"memory-core": {
|
||||
enabled: true,
|
||||
config: {
|
||||
dreaming: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
expected: ["browser", "memory-core"],
|
||||
config: createStartupConfig({
|
||||
enabledPluginIds: ["memory-lancedb"],
|
||||
memorySlot: "memory-lancedb",
|
||||
}),
|
||||
expected: ["demo-channel", "browser", "memory-lancedb"],
|
||||
});
|
||||
});
|
||||
|
||||
it("includes the selected memory-slot plugin and memory-core when dreaming is enabled", () => {
|
||||
it("normalizes the raw memory slot id before startup filtering", () => {
|
||||
expectStartupPluginIdsCase({
|
||||
config: {
|
||||
plugins: {
|
||||
slots: {
|
||||
memory: "memory-lancedb",
|
||||
},
|
||||
entries: {
|
||||
"memory-core": {
|
||||
enabled: true,
|
||||
},
|
||||
"memory-lancedb": {
|
||||
enabled: true,
|
||||
config: {
|
||||
dreaming: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
expected: ["demo-channel", "browser", "memory-core", "memory-lancedb"],
|
||||
config: createStartupConfig({
|
||||
enabledPluginIds: ["memory-core"],
|
||||
memorySlot: "Memory-Core",
|
||||
}),
|
||||
expected: ["demo-channel", "browser", "memory-core"],
|
||||
});
|
||||
});
|
||||
|
||||
it("does not bypass activation policy for dreaming startup owners", () => {
|
||||
it("does not include non-selected memory plugins only because they are enabled", () => {
|
||||
expectStartupPluginIdsCase({
|
||||
config: {
|
||||
channels: {},
|
||||
plugins: {
|
||||
slots: {
|
||||
memory: "memory-lancedb",
|
||||
},
|
||||
entries: {
|
||||
"memory-lancedb": {
|
||||
enabled: false,
|
||||
config: {
|
||||
dreaming: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
expected: ["browser"],
|
||||
config: createStartupConfig({
|
||||
enabledPluginIds: ["memory-lancedb"],
|
||||
}),
|
||||
expected: ["demo-channel", "browser"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "../memory-host-sdk/dreaming.js";
|
||||
import {
|
||||
createPluginActivationSource,
|
||||
normalizePluginId,
|
||||
normalizePluginsConfig,
|
||||
resolveEffectivePluginActivationState,
|
||||
} from "./config-state.js";
|
||||
@@ -29,6 +30,10 @@ function hasRuntimeContractSurface(plugin: PluginManifestRecord): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isGatewayStartupMemoryPlugin(plugin: PluginManifestRecord): boolean {
|
||||
return hasKind(plugin.kind, "memory");
|
||||
}
|
||||
|
||||
function isGatewayStartupSidecar(plugin: PluginManifestRecord): boolean {
|
||||
return plugin.channels.length === 0 && !hasRuntimeContractSurface(plugin);
|
||||
}
|
||||
@@ -44,6 +49,33 @@ function resolveGatewayStartupDreamingPluginIds(config: OpenClawConfig): Set<str
|
||||
return new Set(["memory-core", resolveMemoryDreamingPluginId(config)]);
|
||||
}
|
||||
|
||||
function resolveExplicitMemorySlotStartupPluginId(
|
||||
config: OpenClawConfig,
|
||||
): string | undefined {
|
||||
const configuredSlot = config.plugins?.slots?.memory?.trim();
|
||||
if (!configuredSlot || configuredSlot.toLowerCase() === "none") {
|
||||
return undefined;
|
||||
}
|
||||
return normalizePluginId(configuredSlot);
|
||||
}
|
||||
|
||||
function shouldConsiderForGatewayStartup(params: {
|
||||
plugin: PluginManifestRecord;
|
||||
startupDreamingPluginIds: ReadonlySet<string>;
|
||||
explicitMemorySlotStartupPluginId?: string;
|
||||
}): boolean {
|
||||
if (isGatewayStartupSidecar(params.plugin)) {
|
||||
return true;
|
||||
}
|
||||
if (!isGatewayStartupMemoryPlugin(params.plugin)) {
|
||||
return false;
|
||||
}
|
||||
if (params.startupDreamingPluginIds.has(params.plugin.id)) {
|
||||
return true;
|
||||
}
|
||||
return params.explicitMemorySlotStartupPluginId === params.plugin.id;
|
||||
}
|
||||
|
||||
export function resolveChannelPluginIds(params: {
|
||||
config: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
@@ -113,6 +145,9 @@ export function resolveGatewayStartupPluginIds(params: {
|
||||
config: params.activationSourceConfig ?? params.config,
|
||||
});
|
||||
const startupDreamingPluginIds = resolveGatewayStartupDreamingPluginIds(params.config);
|
||||
const explicitMemorySlotStartupPluginId = resolveExplicitMemorySlotStartupPluginId(
|
||||
params.activationSourceConfig ?? params.config,
|
||||
);
|
||||
return loadPluginManifestRegistry({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
@@ -122,6 +157,15 @@ export function resolveGatewayStartupPluginIds(params: {
|
||||
if (plugin.channels.some((channelId) => configuredChannelIds.has(channelId))) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
!shouldConsiderForGatewayStartup({
|
||||
plugin,
|
||||
startupDreamingPluginIds,
|
||||
explicitMemorySlotStartupPluginId,
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const activationState = resolveEffectivePluginActivationState({
|
||||
id: plugin.id,
|
||||
origin: plugin.origin,
|
||||
@@ -130,22 +174,13 @@ export function resolveGatewayStartupPluginIds(params: {
|
||||
enabledByDefault: plugin.enabledByDefault,
|
||||
activationSource,
|
||||
});
|
||||
const isAllowedStartupActivation = (): boolean => {
|
||||
if (!activationState.enabled) {
|
||||
return false;
|
||||
}
|
||||
if (plugin.origin !== "bundled") {
|
||||
return activationState.explicitlyEnabled;
|
||||
}
|
||||
return activationState.source === "explicit" || activationState.source === "default";
|
||||
};
|
||||
if (startupDreamingPluginIds.has(plugin.id)) {
|
||||
return isAllowedStartupActivation();
|
||||
}
|
||||
if (!isGatewayStartupSidecar(plugin)) {
|
||||
if (!activationState.enabled) {
|
||||
return false;
|
||||
}
|
||||
return isAllowedStartupActivation();
|
||||
if (plugin.origin !== "bundled") {
|
||||
return activationState.explicitlyEnabled;
|
||||
}
|
||||
return activationState.source === "explicit" || activationState.source === "default";
|
||||
})
|
||||
.map((plugin) => plugin.id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user