mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 14:57:27 +00:00
fix: sync built-in channel enablement across config paths
This commit is contained in:
@@ -4,7 +4,7 @@ import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import {
|
||||
normalizePluginsConfig,
|
||||
resolveEnableState,
|
||||
resolveEffectiveEnableState,
|
||||
resolveMemorySlotDecision,
|
||||
} from "../../plugins/config-state.js";
|
||||
import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js";
|
||||
@@ -36,7 +36,12 @@ export function resolvePluginSkillDirs(params: {
|
||||
if (!record.skills || record.skills.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const enableState = resolveEnableState(record.id, record.origin, normalizedPlugins);
|
||||
const enableState = resolveEffectiveEnableState({
|
||||
id: record.id,
|
||||
origin: record.origin,
|
||||
config: normalizedPlugins,
|
||||
rootConfig: params.config,
|
||||
});
|
||||
if (!enableState.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -29,4 +29,40 @@ describe("setPluginEnabledInConfig", () => {
|
||||
enabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps built-in channel and plugin entry flags in sync", () => {
|
||||
const config = {
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
dmPolicy: "open",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const disabled = setPluginEnabledInConfig(config, "telegram", false);
|
||||
expect(disabled.channels?.telegram).toEqual({
|
||||
enabled: false,
|
||||
dmPolicy: "open",
|
||||
});
|
||||
expect(disabled.plugins?.entries?.telegram).toEqual({
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
const reenabled = setPluginEnabledInConfig(disabled, "telegram", true);
|
||||
expect(reenabled.channels?.telegram).toEqual({
|
||||
enabled: true,
|
||||
dmPolicy: "open",
|
||||
});
|
||||
expect(reenabled.plugins?.entries?.telegram).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,21 +1 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
|
||||
export function setPluginEnabledInConfig(
|
||||
config: OpenClawConfig,
|
||||
pluginId: string,
|
||||
enabled: boolean,
|
||||
): OpenClawConfig {
|
||||
return {
|
||||
...config,
|
||||
plugins: {
|
||||
...config.plugins,
|
||||
entries: {
|
||||
...config.plugins?.entries,
|
||||
[pluginId]: {
|
||||
...(config.plugins?.entries?.[pluginId] as object | undefined),
|
||||
enabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
export { setPluginEnabledInConfig } from "../plugins/toggle-config.js";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent
|
||||
import { CHANNEL_IDS, normalizeChatChannelId } from "../channels/registry.js";
|
||||
import {
|
||||
normalizePluginsConfig,
|
||||
resolveEnableState,
|
||||
resolveEffectiveEnableState,
|
||||
resolveMemorySlotDecision,
|
||||
} from "../plugins/config-state.js";
|
||||
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||
@@ -373,7 +373,12 @@ function validateConfigObjectWithPluginsBase(
|
||||
const entry = normalizedPlugins.entries[pluginId];
|
||||
const entryHasConfig = Boolean(entry?.config);
|
||||
|
||||
const enableState = resolveEnableState(pluginId, record.origin, normalizedPlugins);
|
||||
const enableState = resolveEffectiveEnableState({
|
||||
id: pluginId,
|
||||
origin: record.origin,
|
||||
config: normalizedPlugins,
|
||||
rootConfig: config,
|
||||
});
|
||||
let enabled = enableState.enabled;
|
||||
let reason = enableState.reason;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizePluginsConfig } from "./config-state.js";
|
||||
import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js";
|
||||
|
||||
describe("normalizePluginsConfig", () => {
|
||||
it("uses default memory slot when not specified", () => {
|
||||
@@ -48,3 +48,48 @@ describe("normalizePluginsConfig", () => {
|
||||
expect(result.slots.memory).toBe("memory-core");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveEffectiveEnableState", () => {
|
||||
it("enables bundled channels when channels.<id>.enabled=true", () => {
|
||||
const normalized = normalizePluginsConfig({
|
||||
enabled: true,
|
||||
});
|
||||
const state = resolveEffectiveEnableState({
|
||||
id: "telegram",
|
||||
origin: "bundled",
|
||||
config: normalized,
|
||||
rootConfig: {
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(state).toEqual({ enabled: true });
|
||||
});
|
||||
|
||||
it("keeps explicit plugin-level disable authoritative", () => {
|
||||
const normalized = normalizePluginsConfig({
|
||||
enabled: true,
|
||||
entries: {
|
||||
telegram: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
const state = resolveEffectiveEnableState({
|
||||
id: "telegram",
|
||||
origin: "bundled",
|
||||
config: normalized,
|
||||
rootConfig: {
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(state).toEqual({ enabled: false, reason: "disabled in config" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeChatChannelId } from "../channels/registry.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { PluginRecord } from "./registry.js";
|
||||
import { defaultSlotIdForKey } from "./slots.js";
|
||||
@@ -194,6 +195,42 @@ export function resolveEnableState(
|
||||
return { enabled: true };
|
||||
}
|
||||
|
||||
export function isBundledChannelEnabledByChannelConfig(
|
||||
cfg: OpenClawConfig | undefined,
|
||||
pluginId: string,
|
||||
): boolean {
|
||||
if (!cfg) {
|
||||
return false;
|
||||
}
|
||||
const channelId = normalizeChatChannelId(pluginId);
|
||||
if (!channelId) {
|
||||
return false;
|
||||
}
|
||||
const channels = cfg.channels as Record<string, unknown> | undefined;
|
||||
const entry = channels?.[channelId];
|
||||
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
||||
return false;
|
||||
}
|
||||
return (entry as Record<string, unknown>).enabled === true;
|
||||
}
|
||||
|
||||
export function resolveEffectiveEnableState(params: {
|
||||
id: string;
|
||||
origin: PluginRecord["origin"];
|
||||
config: NormalizedPluginsConfig;
|
||||
rootConfig?: OpenClawConfig;
|
||||
}): { enabled: boolean; reason?: string } {
|
||||
const base = resolveEnableState(params.id, params.origin, params.config);
|
||||
if (
|
||||
!base.enabled &&
|
||||
base.reason === "bundled (disabled by default)" &&
|
||||
isBundledChannelEnabledByChannelConfig(params.rootConfig, params.id)
|
||||
) {
|
||||
return { enabled: true };
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
export function resolveMemorySlotDecision(params: {
|
||||
id: string;
|
||||
kind?: string;
|
||||
|
||||
@@ -32,12 +32,12 @@ describe("enablePluginInConfig", () => {
|
||||
expect(result.reason).toBe("blocked by denylist");
|
||||
});
|
||||
|
||||
it("writes built-in channels to channels.<id>.enabled instead of plugins.entries", () => {
|
||||
it("writes built-in channels to channels.<id>.enabled and plugins.entries", () => {
|
||||
const cfg: OpenClawConfig = {};
|
||||
const result = enablePluginInConfig(cfg, "telegram");
|
||||
expect(result.enabled).toBe(true);
|
||||
expect(result.config.channels?.telegram?.enabled).toBe(true);
|
||||
expect(result.config.plugins?.entries?.telegram).toBeUndefined();
|
||||
expect(result.config.plugins?.entries?.telegram?.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it("adds built-in channel id to allowlist when allowlist is configured", () => {
|
||||
@@ -51,4 +51,25 @@ describe("enablePluginInConfig", () => {
|
||||
expect(result.config.channels?.telegram?.enabled).toBe(true);
|
||||
expect(result.config.plugins?.allow).toEqual(["memory-core", "telegram"]);
|
||||
});
|
||||
|
||||
it("re-enables built-in channels after explicit plugin-level disable", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
entries: {
|
||||
telegram: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = enablePluginInConfig(cfg, "telegram");
|
||||
expect(result.enabled).toBe(true);
|
||||
expect(result.config.channels?.telegram?.enabled).toBe(true);
|
||||
expect(result.config.plugins?.entries?.telegram?.enabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { normalizeChatChannelId } from "../channels/registry.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { ensurePluginAllowlisted } from "../config/plugins-allowlist.js";
|
||||
import { setPluginEnabledInConfig } from "./toggle-config.js";
|
||||
|
||||
export type PluginEnableResult = {
|
||||
config: OpenClawConfig;
|
||||
@@ -17,41 +18,7 @@ export function enablePluginInConfig(cfg: OpenClawConfig, pluginId: string): Plu
|
||||
if (cfg.plugins?.deny?.includes(pluginId) || cfg.plugins?.deny?.includes(resolvedId)) {
|
||||
return { config: cfg, enabled: false, reason: "blocked by denylist" };
|
||||
}
|
||||
if (builtInChannelId) {
|
||||
const channels = cfg.channels as Record<string, unknown> | undefined;
|
||||
const existing = channels?.[builtInChannelId];
|
||||
const existingRecord =
|
||||
existing && typeof existing === "object" && !Array.isArray(existing)
|
||||
? (existing as Record<string, unknown>)
|
||||
: {};
|
||||
let next: OpenClawConfig = {
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
[builtInChannelId]: {
|
||||
...existingRecord,
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
next = ensurePluginAllowlisted(next, resolvedId);
|
||||
return { config: next, enabled: true };
|
||||
}
|
||||
|
||||
const entries = {
|
||||
...cfg.plugins?.entries,
|
||||
[resolvedId]: {
|
||||
...(cfg.plugins?.entries?.[resolvedId] as Record<string, unknown> | undefined),
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
let next: OpenClawConfig = {
|
||||
...cfg,
|
||||
plugins: {
|
||||
...cfg.plugins,
|
||||
entries,
|
||||
},
|
||||
};
|
||||
let next = setPluginEnabledInConfig(cfg, resolvedId, true);
|
||||
next = ensurePluginAllowlisted(next, resolvedId);
|
||||
return { config: next, enabled: true };
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createJiti } from "jiti";
|
||||
import { normalizeChatChannelId } from "../channels/registry.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
@@ -12,7 +11,7 @@ import { clearPluginCommands } from "./commands.js";
|
||||
import {
|
||||
applyTestPluginDefaults,
|
||||
normalizePluginsConfig,
|
||||
resolveEnableState,
|
||||
resolveEffectiveEnableState,
|
||||
resolveMemorySlotDecision,
|
||||
type NormalizedPluginsConfig,
|
||||
} from "./config-state.js";
|
||||
@@ -176,19 +175,6 @@ function createPluginRecord(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function isBundledChannelEnabledByChannelConfig(cfg: OpenClawConfig, pluginId: string): boolean {
|
||||
const channelId = normalizeChatChannelId(pluginId);
|
||||
if (!channelId) {
|
||||
return false;
|
||||
}
|
||||
const channels = cfg.channels as Record<string, unknown> | undefined;
|
||||
const entry = channels?.[channelId];
|
||||
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
||||
return false;
|
||||
}
|
||||
return (entry as Record<string, unknown>).enabled === true;
|
||||
}
|
||||
|
||||
function recordPluginError(params: {
|
||||
logger: PluginLogger;
|
||||
registry: PluginRegistry;
|
||||
@@ -486,14 +472,12 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
continue;
|
||||
}
|
||||
|
||||
let enableState = resolveEnableState(pluginId, candidate.origin, normalized);
|
||||
if (
|
||||
!enableState.enabled &&
|
||||
enableState.reason === "bundled (disabled by default)" &&
|
||||
isBundledChannelEnabledByChannelConfig(cfg, pluginId)
|
||||
) {
|
||||
enableState = { enabled: true };
|
||||
}
|
||||
const enableState = resolveEffectiveEnableState({
|
||||
id: pluginId,
|
||||
origin: candidate.origin,
|
||||
config: normalized,
|
||||
rootConfig: cfg,
|
||||
});
|
||||
const entry = normalized.entries[pluginId];
|
||||
const record = createPluginRecord({
|
||||
id: pluginId,
|
||||
|
||||
47
src/plugins/toggle-config.ts
Normal file
47
src/plugins/toggle-config.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { normalizeChatChannelId } from "../channels/registry.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
|
||||
export function setPluginEnabledInConfig(
|
||||
config: OpenClawConfig,
|
||||
pluginId: string,
|
||||
enabled: boolean,
|
||||
): OpenClawConfig {
|
||||
const builtInChannelId = normalizeChatChannelId(pluginId);
|
||||
const resolvedId = builtInChannelId ?? pluginId;
|
||||
|
||||
const next: OpenClawConfig = {
|
||||
...config,
|
||||
plugins: {
|
||||
...config.plugins,
|
||||
entries: {
|
||||
...config.plugins?.entries,
|
||||
[resolvedId]: {
|
||||
...(config.plugins?.entries?.[resolvedId] as object | undefined),
|
||||
enabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (!builtInChannelId) {
|
||||
return next;
|
||||
}
|
||||
|
||||
const channels = config.channels as Record<string, unknown> | undefined;
|
||||
const existing = channels?.[builtInChannelId];
|
||||
const existingRecord =
|
||||
existing && typeof existing === "object" && !Array.isArray(existing)
|
||||
? (existing as Record<string, unknown>)
|
||||
: {};
|
||||
|
||||
return {
|
||||
...next,
|
||||
channels: {
|
||||
...config.channels,
|
||||
[builtInChannelId]: {
|
||||
...existingRecord,
|
||||
enabled,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user