mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:10:43 +00:00
fix: honor source plugin activation at startup
This commit is contained in:
@@ -188,6 +188,34 @@ describe("applyPluginAutoEnable core", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does not load disabled setup plugin manifests when another setup signal exists", () => {
|
||||
const readFileSync = vi.spyOn(fs, "readFileSync");
|
||||
|
||||
const result = applyPluginAutoEnable({
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["telegram"],
|
||||
entries: {
|
||||
browser: { enabled: false },
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
allow: ["browser"],
|
||||
},
|
||||
},
|
||||
env,
|
||||
});
|
||||
|
||||
expect(result.config.plugins?.allow).toEqual(["telegram"]);
|
||||
expect(result.config.plugins?.entries?.browser?.enabled).toBe(false);
|
||||
expect(result.changes).toEqual([]);
|
||||
expect(
|
||||
readFileSync.mock.calls.some(
|
||||
([filePath]) => typeof filePath === "string" && filePath.endsWith("openclaw.plugin.json"),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("still treats a non-disabled browser plugin entry as setup auto-enable input", () => {
|
||||
const result = applyPluginAutoEnable({
|
||||
config: {
|
||||
|
||||
@@ -351,7 +351,7 @@ function collectConfiguredPluginEntryIds(cfg: OpenClawConfig): string[] {
|
||||
}
|
||||
return Object.keys(entries)
|
||||
.map((pluginId) => pluginId.trim())
|
||||
.filter(Boolean);
|
||||
.filter((pluginId) => pluginId && !isPluginEntryExplicitlyDisabled(cfg, pluginId));
|
||||
}
|
||||
|
||||
function hasOwnPluginEntry(cfg: OpenClawConfig, pluginId: string): boolean {
|
||||
@@ -359,16 +359,22 @@ function hasOwnPluginEntry(cfg: OpenClawConfig, pluginId: string): boolean {
|
||||
return !!entries && typeof entries === "object" && Object.hasOwn(entries, pluginId);
|
||||
}
|
||||
|
||||
function isPluginEntryExplicitlyDisabled(cfg: OpenClawConfig, pluginId: string): boolean {
|
||||
return cfg.plugins?.entries?.[pluginId]?.enabled === false;
|
||||
}
|
||||
|
||||
function hasNonDisabledPluginEntry(cfg: OpenClawConfig, pluginId: string): boolean {
|
||||
if (!hasOwnPluginEntry(cfg, pluginId)) {
|
||||
return false;
|
||||
}
|
||||
const entry = cfg.plugins?.entries?.[pluginId];
|
||||
return !isRecord(entry) || entry.enabled !== false;
|
||||
return !isPluginEntryExplicitlyDisabled(cfg, pluginId);
|
||||
}
|
||||
|
||||
function hasBrowserSetupAutoEnableRelevantConfig(cfg: OpenClawConfig): boolean {
|
||||
if (isRecord(cfg.browser) && cfg.browser.enabled !== false) {
|
||||
if (cfg.browser?.enabled === false || isPluginEntryExplicitlyDisabled(cfg, "browser")) {
|
||||
return false;
|
||||
}
|
||||
if (isRecord(cfg.browser)) {
|
||||
return true;
|
||||
}
|
||||
if (hasNonDisabledPluginEntry(cfg, "browser")) {
|
||||
@@ -378,6 +384,9 @@ function hasBrowserSetupAutoEnableRelevantConfig(cfg: OpenClawConfig): boolean {
|
||||
}
|
||||
|
||||
function hasAcpxSetupAutoEnableRelevantConfig(cfg: OpenClawConfig): boolean {
|
||||
if (isPluginEntryExplicitlyDisabled(cfg, "acpx")) {
|
||||
return false;
|
||||
}
|
||||
if (!isRecord(cfg.acp)) {
|
||||
return false;
|
||||
}
|
||||
@@ -390,6 +399,9 @@ function hasAcpxSetupAutoEnableRelevantConfig(cfg: OpenClawConfig): boolean {
|
||||
}
|
||||
|
||||
function hasXaiSetupAutoEnableRelevantConfig(cfg: OpenClawConfig): boolean {
|
||||
if (isPluginEntryExplicitlyDisabled(cfg, "xai")) {
|
||||
return false;
|
||||
}
|
||||
const pluginConfig = cfg.plugins?.entries?.xai?.config;
|
||||
return (
|
||||
(isRecord(pluginConfig) &&
|
||||
|
||||
@@ -47,6 +47,21 @@ function installGatewayPluginRuntimeEnvironment(cfg: OpenClawConfig) {
|
||||
setGatewayNodesRuntime(createGatewayNodesRuntime());
|
||||
}
|
||||
|
||||
function applyActivationSectionsToRuntimeConfig(params: {
|
||||
runtimeConfig: OpenClawConfig;
|
||||
activationConfig: OpenClawConfig;
|
||||
}): OpenClawConfig {
|
||||
return {
|
||||
...params.runtimeConfig,
|
||||
...(params.activationConfig.channels !== undefined
|
||||
? { channels: params.activationConfig.channels }
|
||||
: {}),
|
||||
...(params.activationConfig.plugins !== undefined
|
||||
? { plugins: params.activationConfig.plugins }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
function logGatewayPluginDiagnostics(params: {
|
||||
diagnostics: PluginRegistry["diagnostics"];
|
||||
log: Pick<GatewayPluginBootstrapLog, "error" | "info">;
|
||||
@@ -78,7 +93,13 @@ export function prepareGatewayPluginLoad(params: GatewayPluginBootstrapParams) {
|
||||
? { manifestRegistry: params.pluginLookUpTable.manifestRegistry }
|
||||
: {}),
|
||||
});
|
||||
const resolvedConfig = autoEnabled.config;
|
||||
const resolvedConfig =
|
||||
activationSourceConfig === params.cfg
|
||||
? autoEnabled.config
|
||||
: applyActivationSectionsToRuntimeConfig({
|
||||
runtimeConfig: params.cfg,
|
||||
activationConfig: autoEnabled.config,
|
||||
});
|
||||
installGatewayPluginRuntimeEnvironment(resolvedConfig);
|
||||
const loaded = loadGatewayPlugins({
|
||||
cfg: resolvedConfig,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
|
||||
const applyPluginAutoEnable = vi.hoisted(() =>
|
||||
vi.fn((params: { config: unknown }) => ({
|
||||
@@ -193,6 +194,59 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("derives startup activation from source config instead of runtime plugin defaults", async () => {
|
||||
const sourceConfig = {
|
||||
plugins: {
|
||||
allow: ["bench-plugin"],
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const runtimeConfig = {
|
||||
plugins: {
|
||||
allow: ["bench-plugin"],
|
||||
entries: {
|
||||
"memory-core": {
|
||||
config: {
|
||||
dreaming: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const log = createLog();
|
||||
const { prepareGatewayPluginBootstrap } = await import("./server-startup-plugins.js");
|
||||
|
||||
await prepareGatewayPluginBootstrap({
|
||||
cfgAtStart: runtimeConfig,
|
||||
activationSourceConfig: sourceConfig,
|
||||
startupRuntimeConfig: runtimeConfig,
|
||||
minimalTestGateway: false,
|
||||
log,
|
||||
});
|
||||
|
||||
expect(applyPluginAutoEnable).toHaveBeenCalledWith({
|
||||
config: sourceConfig,
|
||||
env: process.env,
|
||||
});
|
||||
expect(loadPluginLookUpTable).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
activationSourceConfig: sourceConfig,
|
||||
config: expect.objectContaining({
|
||||
plugins: sourceConfig.plugins,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(loadGatewayStartupPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
activationSourceConfig: sourceConfig,
|
||||
cfg: expect.objectContaining({
|
||||
plugins: sourceConfig.plugins,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to per-plugin runtime-deps installs after failed pre-start scan", async () => {
|
||||
scanBundledPluginRuntimeDeps.mockImplementationOnce(() => {
|
||||
throw new Error("unsupported runtime dependency spec");
|
||||
|
||||
@@ -23,6 +23,21 @@ type GatewayPluginBootstrapLog = {
|
||||
debug: (message: string) => void;
|
||||
};
|
||||
|
||||
function applyActivationSectionsToRuntimeConfig(params: {
|
||||
runtimeConfig: OpenClawConfig;
|
||||
activationConfig: OpenClawConfig;
|
||||
}): OpenClawConfig {
|
||||
return {
|
||||
...params.runtimeConfig,
|
||||
...(params.activationConfig.channels !== undefined
|
||||
? { channels: params.activationConfig.channels }
|
||||
: {}),
|
||||
...(params.activationConfig.plugins !== undefined
|
||||
? { plugins: params.activationConfig.plugins }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
async function prestageGatewayBundledRuntimeDeps(params: {
|
||||
cfg: OpenClawConfig;
|
||||
pluginIds: readonly string[];
|
||||
@@ -92,10 +107,12 @@ async function prestageGatewayBundledRuntimeDeps(params: {
|
||||
|
||||
export async function prepareGatewayPluginBootstrap(params: {
|
||||
cfgAtStart: OpenClawConfig;
|
||||
activationSourceConfig?: OpenClawConfig;
|
||||
startupRuntimeConfig: OpenClawConfig;
|
||||
minimalTestGateway: boolean;
|
||||
log: GatewayPluginBootstrapLog;
|
||||
}) {
|
||||
const activationSourceConfig = params.activationSourceConfig ?? params.cfgAtStart;
|
||||
const startupMaintenanceConfig =
|
||||
params.cfgAtStart.channels === undefined && params.startupRuntimeConfig.channels !== undefined
|
||||
? {
|
||||
@@ -130,10 +147,13 @@ export async function prepareGatewayPluginBootstrap(params: {
|
||||
|
||||
const gatewayPluginConfig = params.minimalTestGateway
|
||||
? params.cfgAtStart
|
||||
: applyPluginAutoEnable({
|
||||
config: params.cfgAtStart,
|
||||
env: process.env,
|
||||
}).config;
|
||||
: applyActivationSectionsToRuntimeConfig({
|
||||
runtimeConfig: params.cfgAtStart,
|
||||
activationConfig: applyPluginAutoEnable({
|
||||
config: activationSourceConfig,
|
||||
env: process.env,
|
||||
}).config,
|
||||
});
|
||||
const defaultAgentId = resolveDefaultAgentId(gatewayPluginConfig);
|
||||
const defaultWorkspaceDir = resolveAgentWorkspaceDir(gatewayPluginConfig, defaultAgentId);
|
||||
const pluginLookUpTable = params.minimalTestGateway
|
||||
@@ -142,7 +162,7 @@ export async function prepareGatewayPluginBootstrap(params: {
|
||||
config: gatewayPluginConfig,
|
||||
workspaceDir: defaultWorkspaceDir,
|
||||
env: process.env,
|
||||
activationSourceConfig: params.cfgAtStart,
|
||||
activationSourceConfig,
|
||||
});
|
||||
const deferredConfiguredChannelPluginIds = [
|
||||
...(pluginLookUpTable?.startup.configuredDeferredChannelPluginIds ?? []),
|
||||
@@ -162,7 +182,7 @@ export async function prepareGatewayPluginBootstrap(params: {
|
||||
});
|
||||
({ pluginRegistry, gatewayMethods: baseGatewayMethods } = loadGatewayStartupPlugins({
|
||||
cfg: gatewayPluginConfig,
|
||||
activationSourceConfig: params.cfgAtStart,
|
||||
activationSourceConfig,
|
||||
workspaceDir: defaultWorkspaceDir,
|
||||
log: params.log,
|
||||
coreGatewayMethodNames: baseMethods,
|
||||
|
||||
@@ -336,6 +336,7 @@ export async function startGatewayServer(
|
||||
let cfgAtStart: OpenClawConfig;
|
||||
let startupInternalWriteHash: string | null = null;
|
||||
let startupLastGoodSnapshot = configSnapshot;
|
||||
const startupActivationSourceConfig = configSnapshot.sourceConfig;
|
||||
const startupRuntimeConfig = applyConfigOverrides(configSnapshot.config);
|
||||
const authBootstrap = await startupTrace.measure("config.auth", () =>
|
||||
prepareGatewayStartupConfig({
|
||||
@@ -408,6 +409,7 @@ export async function startGatewayServer(
|
||||
const pluginBootstrap = await startupTrace.measure("plugins.bootstrap", () =>
|
||||
prepareGatewayPluginBootstrap({
|
||||
cfgAtStart,
|
||||
activationSourceConfig: startupActivationSourceConfig,
|
||||
startupRuntimeConfig,
|
||||
minimalTestGateway,
|
||||
log,
|
||||
@@ -856,6 +858,7 @@ export async function startGatewayServer(
|
||||
const { reloadDeferredGatewayPlugins } = await import("./server-plugin-bootstrap.js");
|
||||
({ pluginRegistry, gatewayMethods: baseGatewayMethods } = reloadDeferredGatewayPlugins({
|
||||
cfg: gatewayPluginConfigAtStart,
|
||||
activationSourceConfig: startupActivationSourceConfig,
|
||||
workspaceDir: defaultWorkspaceDir,
|
||||
log,
|
||||
coreGatewayMethodNames: baseMethods,
|
||||
|
||||
@@ -488,6 +488,42 @@ describe("resolveGatewayStartupPluginIds", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not let runtime-default plugin entries bypass the authored startup allowlist", () => {
|
||||
const activationSourceConfig = {
|
||||
channels: {},
|
||||
plugins: {
|
||||
allow: ["bench-plugin"],
|
||||
entries: {
|
||||
browser: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const runtimeConfig = {
|
||||
...activationSourceConfig,
|
||||
plugins: {
|
||||
...activationSourceConfig.plugins,
|
||||
entries: {
|
||||
...activationSourceConfig.plugins?.entries,
|
||||
"memory-core": {
|
||||
config: {
|
||||
dreaming: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expectStartupPluginIdsCase({
|
||||
config: runtimeConfig,
|
||||
activationSourceConfig,
|
||||
expected: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("starts bundled sidecars selected by root config activation paths", () => {
|
||||
const rawConfig = {
|
||||
browser: {
|
||||
|
||||
Reference in New Issue
Block a user