fix(config): tolerate missing channel metadata during auto-enable

This commit is contained in:
Peter Steinberger
2026-04-23 00:50:34 +01:00
parent 53e822f407
commit 5a5aa3a178
4 changed files with 92 additions and 2 deletions

View File

@@ -36,6 +36,7 @@ Docs: https://docs.openclaw.ai
- Agents/MCP: keep `mcp.servers` and bundle MCP tools available in Pi embedded
`coding` and `messaging` sessions while preserving `minimal` profile and
`tools.deny: ["bundle-mcp"]` opt-out behavior. Fixes #68875 and #68818.
- Plugins/startup: tolerate transient bundled-channel catalog/metadata drift while auto-enabling configured plugins, so CLI and gateway startup no longer crash when a channel id is known but its display metadata is unavailable.
- CLI/Claude: report CLI-backed reply runs as streaming while Claude/Codex CLI turns are still in flight, so WebChat keeps visible response state until the backend finishes. Fixes #70125.
- Codex harness: rotate the shared app-server websocket client when the configured bearer token changes, so auth-token refreshes reconnect with the new `Authorization` header instead of reusing a stale socket. (#70328) Thanks @Lucenx9.
- Telegram/sandbox: keep Telegram bot DMs on per-account sender session keys even when `session.dmScope=main`, so sandbox/tool policy can distinguish Telegram-originated direct chats from the agent main session.

View File

@@ -0,0 +1,89 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
import { cleanupTrackedTempDirs } from "../plugins/test-helpers/fs-fixtures.js";
const tempDirs: string[] = [];
function makeTempDir(): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-prefer-over-"));
tempDirs.push(dir);
return dir;
}
function writeBundledChannelPackage(rootDir: string, channelId: string): void {
const pluginDir = path.join(rootDir, channelId);
fs.mkdirSync(pluginDir, { recursive: true });
fs.writeFileSync(
path.join(pluginDir, "package.json"),
JSON.stringify({
openclaw: {
channel: {
id: channelId,
label: "Cache Drift",
selectionLabel: "Cache Drift",
docsPath: `/channels/${channelId}`,
blurb: "Cache drift fixture",
},
},
}),
"utf-8",
);
}
const EMPTY_MANIFEST_REGISTRY: PluginManifestRegistry = {
plugins: [],
diagnostics: [],
};
afterEach(() => {
vi.unstubAllEnvs();
vi.resetModules();
cleanupTrackedTempDirs(tempDirs);
});
describe("plugin auto-enable preferOver", () => {
it("tolerates bundled channel id metadata drift during auto-enable", async () => {
vi.resetModules();
const rootDir = makeTempDir();
const channelId = "cache-drift-channel";
writeBundledChannelPackage(rootDir, channelId);
vi.stubEnv("OPENCLAW_BUNDLED_PLUGINS_DIR", rootDir);
const { normalizeChatChannelId } = await import("../channels/ids.js");
expect(normalizeChatChannelId(channelId)).toBe(channelId);
vi.stubEnv("OPENCLAW_BUNDLED_PLUGINS_DIR", path.join(rootDir, "missing"));
const { materializePluginAutoEnableCandidates } = await import("./plugin-auto-enable.js");
const result = materializePluginAutoEnableCandidates({
config: {
channels: {
[channelId]: { token: "configured" },
fallback: { token: "configured" },
},
},
candidates: [
{
pluginId: channelId,
kind: "channel-configured",
channelId,
},
{
pluginId: "fallback",
kind: "channel-configured",
channelId: "fallback",
},
],
env: {
OPENCLAW_STATE_DIR: path.join(rootDir, "state"),
OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(rootDir, "missing"),
},
manifestRegistry: EMPTY_MANIFEST_REGISTRY,
});
expect(result.config.channels?.[channelId]?.enabled).toBe(true);
});
});

View File

@@ -96,7 +96,7 @@ function resolveBuiltInChannelPreferOver(channelId: string): readonly string[] {
if (!builtInChannelId) {
return [];
}
return getChatChannelMeta(builtInChannelId).preferOver ?? [];
return getChatChannelMeta(builtInChannelId)?.preferOver ?? [];
}
function resolvePreferredOverIds(

View File

@@ -684,7 +684,7 @@ function resolveChannelAutoEnableDisplayLabel(
): string | undefined {
const builtInChannelId = normalizeChatChannelId(entry.channelId);
if (builtInChannelId) {
return getChatChannelMeta(builtInChannelId).label;
return getChatChannelMeta(builtInChannelId)?.label;
}
const plugin = manifestRegistry.plugins.find((record) => record.id === entry.pluginId);
return plugin?.channelConfigs?.[entry.channelId]?.label ?? plugin?.channelCatalogMeta?.label;