Config: split static channel configured helper

This commit is contained in:
Peter Steinberger
2026-04-07 13:07:06 +08:00
parent 2091334399
commit df58f73a2d
3 changed files with 63 additions and 26 deletions

View File

@@ -0,0 +1,44 @@
import { hasNonEmptyString } from "../infra/outbound/channel-target.js";
import { isRecord } from "../utils.js";
import type { OpenClawConfig } from "./config.js";
const STATIC_ENV_RULES: Record<string, string[] | ((env: NodeJS.ProcessEnv) => boolean)> = {
discord: ["DISCORD_BOT_TOKEN"],
slack: ["SLACK_BOT_TOKEN"],
telegram: ["TELEGRAM_BOT_TOKEN"],
irc: (env) => hasNonEmptyString(env.IRC_HOST) && hasNonEmptyString(env.IRC_NICK),
};
export function resolveChannelConfigRecord(
cfg: OpenClawConfig,
channelId: string,
): Record<string, unknown> | null {
const channels = cfg.channels as Record<string, unknown> | undefined;
const entry = channels?.[channelId];
return isRecord(entry) ? entry : null;
}
export function hasMeaningfulChannelConfigShallow(value: unknown): boolean {
if (!isRecord(value)) {
return false;
}
return Object.keys(value).some((key) => key !== "enabled");
}
export function isStaticallyChannelConfigured(
cfg: OpenClawConfig,
channelId: string,
env: NodeJS.ProcessEnv = process.env,
): boolean {
const staticRule = STATIC_ENV_RULES[channelId];
if (Array.isArray(staticRule)) {
for (const envVar of staticRule) {
if (hasNonEmptyString(env[envVar])) {
return true;
}
}
} else if (staticRule?.(env)) {
return true;
}
return hasMeaningfulChannelConfigShallow(resolveChannelConfigRecord(cfg, channelId));
}

View File

@@ -1,24 +1,12 @@
import { hasMeaningfulChannelConfig } from "../channels/config-presence.js";
import { getBootstrapChannelPlugin } from "../channels/plugins/bootstrap-registry.js";
import { hasBundledChannelConfiguredState } from "../channels/plugins/configured-state.js";
import { hasBundledChannelPersistedAuthState } from "../channels/plugins/persisted-auth-state.js";
import { isRecord } from "../utils.js";
import {
hasMeaningfulChannelConfigShallow,
resolveChannelConfigRecord,
} from "./channel-configured-shared.js";
import type { OpenClawConfig } from "./config.js";
function resolveChannelConfig(
cfg: OpenClawConfig,
channelId: string,
): Record<string, unknown> | null {
const channels = cfg.channels as Record<string, unknown> | undefined;
const entry = channels?.[channelId];
return isRecord(entry) ? entry : null;
}
function isGenericChannelConfigured(cfg: OpenClawConfig, channelId: string): boolean {
const entry = resolveChannelConfig(cfg, channelId);
return hasMeaningfulChannelConfig(entry);
}
export function isChannelConfigured(
cfg: OpenClawConfig,
channelId: string,
@@ -31,7 +19,7 @@ export function isChannelConfigured(
if (pluginPersistedAuthState) {
return true;
}
if (isGenericChannelConfigured(cfg, channelId)) {
if (hasMeaningfulChannelConfigShallow(resolveChannelConfigRecord(cfg, channelId))) {
return true;
}
const plugin = getBootstrapChannelPlugin(channelId);

View File

@@ -20,7 +20,7 @@ describe("plugin activation boundary", () => {
let ambientImportsPromise: Promise<void> | undefined;
let configHelpersPromise:
| Promise<{
isChannelConfigured: typeof import("./config/channel-configured.js").isChannelConfigured;
isStaticallyChannelConfigured: typeof import("./config/channel-configured-shared.js").isStaticallyChannelConfigured;
resolveEnvApiKey: typeof import("./agents/model-auth-env.js").resolveEnvApiKey;
}>
| undefined;
@@ -58,10 +58,10 @@ describe("plugin activation boundary", () => {
function importConfigHelpers() {
configHelpersPromise ??= Promise.all([
import("./config/channel-configured.js"),
import("./config/channel-configured-shared.js"),
import("./agents/model-auth-env.js"),
]).then(([channelConfigured, modelAuthEnv]) => ({
isChannelConfigured: channelConfigured.isChannelConfigured,
isStaticallyChannelConfigured: channelConfigured.isStaticallyChannelConfigured,
resolveEnvApiKey: modelAuthEnv.resolveEnvApiKey,
}));
return configHelpersPromise;
@@ -116,15 +116,20 @@ describe("plugin activation boundary", () => {
});
it("does not load bundled plugins for config and env detection helpers", async () => {
const { isChannelConfigured, resolveEnvApiKey } = await importConfigHelpers();
const { isStaticallyChannelConfigured, resolveEnvApiKey } = await importConfigHelpers();
expect(isChannelConfigured({}, "telegram", { TELEGRAM_BOT_TOKEN: "token" })).toBe(true);
expect(isChannelConfigured({}, "discord", { DISCORD_BOT_TOKEN: "token" })).toBe(true);
expect(isChannelConfigured({}, "slack", { SLACK_BOT_TOKEN: "xoxb-test" })).toBe(true);
expect(isStaticallyChannelConfigured({}, "telegram", { TELEGRAM_BOT_TOKEN: "token" })).toBe(
true,
);
expect(isStaticallyChannelConfigured({}, "discord", { DISCORD_BOT_TOKEN: "token" })).toBe(true);
expect(isStaticallyChannelConfigured({}, "slack", { SLACK_BOT_TOKEN: "xoxb-test" })).toBe(true);
expect(
isChannelConfigured({}, "irc", { IRC_HOST: "irc.example.com", IRC_NICK: "openclaw" }),
isStaticallyChannelConfigured({}, "irc", {
IRC_HOST: "irc.example.com",
IRC_NICK: "openclaw",
}),
).toBe(true);
expect(isChannelConfigured({}, "whatsapp", {})).toBe(false);
expect(isStaticallyChannelConfigured({}, "whatsapp", {})).toBe(false);
expect(
resolveEnvApiKey("anthropic-vertex", {
ANTHROPIC_VERTEX_USE_GCP_METADATA: "true",