mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 23:20:24 +00:00
fix(onboarding): use scoped plugin snapshots to prevent OOM on low-memory hosts (#46763)
* fix(onboarding): use scoped plugin snapshots to prevent OOM on low-memory hosts Onboarding and channel-add flows previously loaded the full plugin registry, which caused OOM crashes on memory-constrained hosts. This patch introduces scoped, non-activating plugin registry snapshots that load only the selected channel plugin without replacing the running gateway's global state. Key changes: - Add onlyPluginIds and activate options to loadOpenClawPlugins for scoped loads - Add suppressGlobalCommands to plugin registry to avoid leaking commands - Replace full registry reloads in onboarding with per-channel scoped snapshots - Validate command definitions in snapshot loads without writing global registry - Preload configured external plugins via scoped discovery during onboarding Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): add return type annotation to hoisted mock to resolve TS2322 * fix(plugins): enforce cache:false invariant for non-activating snapshot loads * Channels: preserve lazy scoped snapshot import after rebase * Onboarding: scope channel snapshots by plugin id * Catalog: trust manifest ids for channel plugin mapping * Onboarding: preserve scoped setup channel loading * Onboarding: restore built-in adapter fallback --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -10,7 +10,7 @@ import type {
|
||||
import { registerInternalHook } from "../hooks/internal-hooks.js";
|
||||
import type { HookEntry } from "../hooks/types.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { registerPluginCommand } from "./commands.js";
|
||||
import { registerPluginCommand, validatePluginCommandDefinition } from "./commands.js";
|
||||
import { normalizePluginHttpPath } from "./http-path.js";
|
||||
import { findOverlappingPluginHttpRoute } from "./http-route-overlap.js";
|
||||
import { registerPluginInteractiveHandler } from "./interactive.js";
|
||||
@@ -177,6 +177,9 @@ export type PluginRegistryParams = {
|
||||
logger: PluginLogger;
|
||||
coreGatewayHandlers?: GatewayRequestHandlers;
|
||||
runtime: PluginRuntime;
|
||||
// When true, skip writing to the global plugin command registry during register().
|
||||
// Used by non-activating snapshot loads to avoid leaking commands into the running gateway.
|
||||
suppressGlobalCommands?: boolean;
|
||||
};
|
||||
|
||||
type PluginTypedHookPolicy = {
|
||||
@@ -615,19 +618,37 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register with the plugin command system (validates name and checks for duplicates)
|
||||
const result = registerPluginCommand(record.id, command, {
|
||||
pluginName: record.name,
|
||||
pluginRoot: record.rootDir,
|
||||
});
|
||||
if (!result.ok) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `command registration failed: ${result.error}`,
|
||||
// For snapshot (non-activating) loads, record the command locally without touching the
|
||||
// global plugin command registry so running gateway commands stay intact.
|
||||
// We still validate the command definition so diagnostics match the real activation path.
|
||||
// NOTE: cross-plugin duplicate command detection is intentionally skipped here because
|
||||
// snapshot registries are isolated and never write to the global command table. Conflicts
|
||||
// will surface when the plugin is loaded via the normal activation path at gateway startup.
|
||||
if (registryParams.suppressGlobalCommands) {
|
||||
const validationError = validatePluginCommandDefinition(command);
|
||||
if (validationError) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `command registration failed: ${validationError}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const result = registerPluginCommand(record.id, command, {
|
||||
pluginName: record.name,
|
||||
pluginRoot: record.rootDir,
|
||||
});
|
||||
return;
|
||||
if (!result.ok) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `command registration failed: ${result.error}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
record.commands.push(name);
|
||||
|
||||
Reference in New Issue
Block a user