fix(config): allowlist auto-enabled built-in channels when restricted

Co-authored-by: 4rev <4rev@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-02-22 19:30:29 +01:00
parent 772cf7df33
commit 40680432b4
5 changed files with 38 additions and 15 deletions

View File

@@ -34,6 +34,7 @@ Docs: https://docs.openclaw.ai
- Feishu/Plugins: restore bundled Feishu SDK availability for global installs and strip `openclaw: workspace:*` from plugin `devDependencies` during plugin-version sync so npm-installed Feishu plugins do not fail dependency install. (#23611, #23645, #23603)
- Plugins/Install: strip `workspace:*` devDependency entries from copied plugin manifests before `npm install --omit=dev`, preventing `EUNSUPPORTEDPROTOCOL` install failures for npm-published channel plugins (including Feishu and MS Teams).
- Config/Channels: auto-enable built-in channels by writing `channels.<id>.enabled=true` (not `plugins.entries.<id>`), and stop adding built-ins to `plugins.allow`, preventing `plugins.entries.telegram: plugin not found` validation failures.
- Config/Channels: when `plugins.allow` is active, auto-enable/enable flows now also allowlist configured built-in channels so `channels.<id>.enabled=true` cannot remain blocked by restrictive plugin allowlists.
- Plugins/Discovery: ignore scanned extension backup/disabled directory patterns (for example `.backup-*`, `.bak`, `.disabled*`) and move updater backup directories under `.openclaw-install-backups`, preventing duplicate plugin-id collisions from archived copies.
- Dev tooling: prevent `CLAUDE.md` symlink target regressions by excluding CLAUDE symlink sentinels from `oxfmt` and marking them `-text` in `.gitattributes`, so formatter/EOL normalization cannot reintroduce trailing-newline targets. Thanks @vincentkoc.
- Cron: honor `cron.maxConcurrentRuns` in the timer loop so due jobs can execute up to the configured parallelism instead of always running serially. (#11595) Thanks @Takhoffman.

View File

@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import { applyPluginAutoEnable } from "./plugin-auto-enable.js";
describe("applyPluginAutoEnable", () => {
it("auto-enables built-in channels without touching plugins allowlist", () => {
it("auto-enables built-in channels and appends to existing allowlist", () => {
const result = applyPluginAutoEnable({
config: {
channels: { slack: { botToken: "x" } },
@@ -13,10 +13,22 @@ describe("applyPluginAutoEnable", () => {
expect(result.config.channels?.slack?.enabled).toBe(true);
expect(result.config.plugins?.entries?.slack).toBeUndefined();
expect(result.config.plugins?.allow).toEqual(["telegram"]);
expect(result.config.plugins?.allow).toEqual(["telegram", "slack"]);
expect(result.changes.join("\n")).toContain("Slack configured, enabled automatically.");
});
it("does not create plugins.allow when allowlist is unset", () => {
const result = applyPluginAutoEnable({
config: {
channels: { slack: { botToken: "x" } },
},
env: {},
});
expect(result.config.channels?.slack?.enabled).toBe(true);
expect(result.config.plugins?.allow).toBeUndefined();
});
it("ignores channels.modelByChannel for plugin auto-enable", () => {
const result = applyPluginAutoEnable({
config: {

View File

@@ -477,8 +477,7 @@ export function applyPluginAutoEnable(params: {
continue;
}
const allow = next.plugins?.allow;
const allowMissing =
!builtInChannelId && Array.isArray(allow) && !allow.includes(entry.pluginId);
const allowMissing = Array.isArray(allow) && !allow.includes(entry.pluginId);
const alreadyEnabled =
builtInChannelId != null
? (() => {
@@ -498,7 +497,7 @@ export function applyPluginAutoEnable(params: {
continue;
}
next = registerPluginEntry(next, entry.pluginId);
if (!builtInChannelId) {
if (allowMissing || !builtInChannelId) {
next = ensurePluginAllowlisted(next, entry.pluginId);
}
changes.push(formatAutoEnableChange(entry));

View File

@@ -39,4 +39,16 @@ describe("enablePluginInConfig", () => {
expect(result.config.channels?.telegram?.enabled).toBe(true);
expect(result.config.plugins?.entries?.telegram).toBeUndefined();
});
it("adds built-in channel id to allowlist when allowlist is configured", () => {
const cfg: OpenClawConfig = {
plugins: {
allow: ["memory-core"],
},
};
const result = enablePluginInConfig(cfg, "telegram");
expect(result.enabled).toBe(true);
expect(result.config.channels?.telegram?.enabled).toBe(true);
expect(result.config.plugins?.allow).toEqual(["memory-core", "telegram"]);
});
});

View File

@@ -24,19 +24,18 @@ export function enablePluginInConfig(cfg: OpenClawConfig, pluginId: string): Plu
existing && typeof existing === "object" && !Array.isArray(existing)
? (existing as Record<string, unknown>)
: {};
return {
config: {
...cfg,
channels: {
...cfg.channels,
[builtInChannelId]: {
...existingRecord,
enabled: true,
},
let next: OpenClawConfig = {
...cfg,
channels: {
...cfg.channels,
[builtInChannelId]: {
...existingRecord,
enabled: true,
},
},
enabled: true,
};
next = ensurePluginAllowlisted(next, resolvedId);
return { config: next, enabled: true };
}
const entries = {