fix: respect external channel owners in doctor blockers

This commit is contained in:
Peter Steinberger
2026-04-28 02:15:21 +01:00
parent 6a338ba67d
commit 837c4c5f1b
3 changed files with 130 additions and 3 deletions

View File

@@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Cron/providers: preflight local Ollama and OpenAI-compatible provider endpoints before isolated cron agent turns, record unreachable local providers as skipped runs, and cache dead-endpoint probes so many jobs do not hammer the same stopped local server. Fixes #58584. Thanks @jpeghead.
- Doctor/channels: suppress disabled bundled-plugin blocker warnings when a trusted external plugin owns the configured channel, so Lark/Feishu installs no longer get Feishu repair noise after switching to `openclaw-lark`. Fixes #56794. Thanks @wuji-tech-dev.
- CLI/status: show skipped fast-path memory checks as `not checked` and report active custom memory plugin runtime status from `status --json --all` without requiring built-in `agents.defaults.memorySearch`, so plugins such as memory-lancedb-pro and memory-cms no longer look unavailable when their own runtime is healthy. Fixes #56968. Thanks @Tony-ooo and @aderius.
- Gateway/channels: record and log unexpected clean channel monitor exits so channels that return without throwing no longer appear stopped with no error. Fixes #73099. Thanks @balaji1968-kingler.
- Channels/Telegram: centralize polling update tracking so accepted offsets remain durable across restarts, same-process handler failures can still retry, and slow offset writes cannot overwrite newer accepted watermarks. Refs #73115. Thanks @vdruts.

View File

@@ -106,4 +106,105 @@ describe("channel plugin blockers", () => {
},
]);
});
it("does not report a disabled bundled owner when a configured external plugin owns the channel", () => {
vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({
plugins: [
{
id: "feishu",
origin: "bundled",
channels: ["feishu"],
enabledByDefault: true,
},
{
id: "openclaw-lark",
origin: "config",
channels: ["feishu"],
enabledByDefault: false,
channelConfigs: {
feishu: {
schema: {
type: "object",
},
},
},
},
],
diagnostics: [],
} as unknown as ReturnType<typeof manifestRegistry.loadPluginManifestRegistry>);
const hits = scanConfiguredChannelPluginBlockers({
plugins: {
entries: {
feishu: {
enabled: false,
},
"openclaw-lark": {
enabled: true,
},
},
},
channels: {
feishu: {
footer: {
model: false,
},
},
},
});
expect(hits).toEqual([]);
});
it("still reports the disabled bundled owner when an external channel owner is not trusted", () => {
vi.spyOn(manifestRegistry, "loadPluginManifestRegistry").mockReturnValue({
plugins: [
{
id: "feishu",
origin: "bundled",
channels: ["feishu"],
enabledByDefault: true,
},
{
id: "openclaw-lark",
origin: "config",
channels: ["feishu"],
enabledByDefault: false,
channelConfigs: {
feishu: {
schema: {
type: "object",
},
},
},
},
],
diagnostics: [],
} as unknown as ReturnType<typeof manifestRegistry.loadPluginManifestRegistry>);
const hits = scanConfiguredChannelPluginBlockers({
plugins: {
entries: {
feishu: {
enabled: false,
},
},
},
channels: {
feishu: {
footer: {
model: false,
},
},
},
});
expect(hits).toEqual([
{
channelId: "feishu",
pluginId: "feishu",
reason: "disabled in config",
},
]);
});
});

View File

@@ -1,10 +1,14 @@
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
import { listExplicitConfiguredChannelIdsForConfig } from "../../../plugins/channel-plugin-ids.js";
import {
listExplicitConfiguredChannelIdsForConfig,
resolveConfiguredChannelPresencePolicy,
} from "../../../plugins/channel-plugin-ids.js";
import {
normalizePluginsConfig,
resolveEffectivePluginActivationState,
} from "../../../plugins/config-state.js";
import { loadPluginManifestRegistryForPluginRegistry } from "../../../plugins/plugin-registry.js";
import { normalizeOptionalLowercaseString } from "../../../shared/string-coerce.js";
import { sanitizeForLog } from "../../../terminal/ansi.js";
export type ChannelPluginBlockerHit = {
@@ -39,7 +43,11 @@ export function scanConfiguredChannelPluginBlockers(
if (!hasExplicitChannelPluginBlockerConfig(cfg)) {
return [];
}
const configuredChannelIds = new Set(listExplicitConfiguredChannelIdsForConfig(cfg));
const configuredChannelIds = new Set(
listExplicitConfiguredChannelIdsForConfig(cfg)
.map((channelId) => normalizeOptionalLowercaseString(channelId))
.filter((channelId): channelId is string => Boolean(channelId)),
);
if (configuredChannelIds.size === 0) {
return [];
}
@@ -50,6 +58,16 @@ export function scanConfiguredChannelPluginBlockers(
env,
includeDisabled: true,
});
const activeConfiguredChannelIds = new Set(
resolveConfiguredChannelPresencePolicy({
config: cfg,
env,
includePersistedAuthState: false,
manifestRecords: registry.plugins,
})
.filter((entry) => entry.effective)
.map((entry) => entry.channelId),
);
const hits: ChannelPluginBlockerHit[] = [];
for (const plugin of registry.plugins) {
@@ -73,10 +91,17 @@ export function scanConfiguredChannelPluginBlockers(
continue;
}
for (const channelId of plugin.channels) {
for (const rawChannelId of plugin.channels) {
const channelId = normalizeOptionalLowercaseString(rawChannelId);
if (!channelId) {
continue;
}
if (!configuredChannelIds.has(channelId)) {
continue;
}
if (activeConfiguredChannelIds.has(channelId)) {
continue;
}
hits.push({
channelId,
pluginId: plugin.id,