refactor(channels): unify trusted configured-state probes (#130320)

This commit is contained in:
Peter Steinberger
2026-08-26 13:16:24 -07:00
committed by GitHub
parent 81f2d1eb61
commit e668e96034
8 changed files with 87 additions and 98 deletions
+25 -13
View File
@@ -3,15 +3,15 @@ import { createRequire } from "node:module";
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import {
hasBundledChannelConfiguredState,
listBundledChannelIdsWithConfiguredState,
} from "./configured-state.js";
hasBundledChannelPackageState,
listBundledChannelIdsForPackageState,
} from "./package-state-probes.js";
const nodeRequire = createRequire(import.meta.url);
describe("bundled channel configured-state metadata", () => {
it("lists the shipped metadata-first configured-state channels", () => {
expect(listBundledChannelIdsWithConfiguredState()).toEqual([
expect(listBundledChannelIdsForPackageState("configuredState")).toEqual([
"buzz",
"clickclack",
"discord",
@@ -37,28 +37,32 @@ describe("bundled channel configured-state metadata", () => {
it("resolves Discord, Slack, Telegram, and IRC env probes without full plugin loads", () => {
expect(
hasBundledChannelConfiguredState({
hasBundledChannelPackageState({
metadataKey: "configuredState",
channelId: "discord",
cfg: {},
env: { DISCORD_BOT_TOKEN: "token" },
}),
).toBe(true);
expect(
hasBundledChannelConfiguredState({
hasBundledChannelPackageState({
metadataKey: "configuredState",
channelId: "slack",
cfg: {},
env: { SLACK_BOT_TOKEN: "xoxb-test", SLACK_APP_TOKEN: "xapp-test" },
}),
).toBe(true);
expect(
hasBundledChannelConfiguredState({
hasBundledChannelPackageState({
metadataKey: "configuredState",
channelId: "telegram",
cfg: {},
env: { TELEGRAM_BOT_TOKEN: "token" },
}),
).toBe(true);
expect(
hasBundledChannelConfiguredState({
hasBundledChannelPackageState({
metadataKey: "configuredState",
channelId: "irc",
cfg: {},
env: { IRC_HOST: "irc.example.com", IRC_NICK: "openclaw" },
@@ -79,7 +83,9 @@ describe("bundled channel configured-state metadata", () => {
{ channelId: "nextcloud-talk", env: { NEXTCLOUD_TALK_BOT_SECRET: "secret" } },
{ channelId: "zalo", env: { ZALO_WEBHOOK_SECRET: "secret" } },
])("rejects incomplete $channelId environment credentials", ({ channelId, env }) => {
expect(hasBundledChannelConfiguredState({ channelId, cfg: {}, env })).toBe(false);
expect(
hasBundledChannelPackageState({ metadataKey: "configuredState", channelId, cfg: {}, env }),
).toBe(false);
});
it.each([
@@ -109,12 +115,15 @@ describe("bundled channel configured-state metadata", () => {
env: { SYNOLOGY_CHAT_TOKEN: "token", SYNOLOGY_CHAT_INCOMING_URL: "https://example.test" },
},
])("accepts complete $channelId environment credentials", ({ channelId, env }) => {
expect(hasBundledChannelConfiguredState({ channelId, cfg: {}, env })).toBe(true);
expect(
hasBundledChannelPackageState({ metadataKey: "configuredState", channelId, cfg: {}, env }),
).toBe(true);
});
it("keeps explicit blank Teams credentials authoritative over ambient credentials", () => {
expect(
hasBundledChannelConfiguredState({
hasBundledChannelPackageState({
metadataKey: "configuredState",
channelId: "msteams",
cfg: { channels: { msteams: { appId: "", appPassword: "", tenantId: "" } } },
env: {
@@ -168,7 +177,9 @@ describe("bundled channel configured-state metadata", () => {
cfg: OpenClawConfig;
env: NodeJS.ProcessEnv;
}>)("accepts the owner-specific $name contract", ({ channelId, cfg, env }) => {
expect(hasBundledChannelConfiguredState({ channelId, cfg, env })).toBe(true);
expect(
hasBundledChannelPackageState({ metadataKey: "configuredState", channelId, cfg, env }),
).toBe(true);
});
it("uses declarative env metadata without a TypeScript source require hook", () => {
@@ -176,7 +187,8 @@ describe("bundled channel configured-state metadata", () => {
delete nodeRequire.extensions[".ts"];
try {
expect(
hasBundledChannelConfiguredState({
hasBundledChannelPackageState({
metadataKey: "configuredState",
channelId: "discord",
cfg: {},
env: { DISCORD_BOT_TOKEN: "token" },
-38
View File
@@ -1,38 +0,0 @@
/**
* Bundled channel configured-state probes.
*
* Lists and checks bundled channels that can report configured account state.
*/
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { PluginDiscoveryResult } from "../../plugins/discovery.js";
import {
hasBundledChannelPackageState,
listBundledChannelIdsForPackageState,
} from "./package-state-probes.js";
/**
* Lists bundled channel ids that expose configured-state detectors.
*/
export function listBundledChannelIdsWithConfiguredState(
discovery?: PluginDiscoveryResult,
): string[] {
return listBundledChannelIdsForPackageState("configuredState", discovery);
}
/**
* Checks whether a bundled channel reports configured state for the current config.
*/
export function hasBundledChannelConfiguredState(params: {
channelId: string;
cfg: OpenClawConfig;
env?: NodeJS.ProcessEnv;
discovery?: PluginDiscoveryResult;
}): boolean {
return hasBundledChannelPackageState({
metadataKey: "configuredState",
channelId: params.channelId,
cfg: params.cfg,
env: params.env,
discovery: params.discovery,
});
}
@@ -133,6 +133,27 @@ describe("channel package-state probes", () => {
).toBe(false);
});
it.each([
{ name: "PATH", env: { PATH: "/usr/bin" }, configured: false },
{ name: "mixed_case_token", env: { MIXED_CASE_TOKEN: "token" }, configured: true },
])("applies the safe channel env-trigger contract to $name", ({ name, env, configured }) => {
listChannelCatalogEntriesMock.mockReturnValue([
{
...makeBundledChannelCatalogEntry({ pluginId: "env-chat", channelId: "env-chat" }),
channel: { id: "env-chat", configuredState: { env: { allOf: [name] } } },
} satisfies PluginChannelCatalogEntry,
]);
expect(
hasBundledChannelPackageState({
metadataKey: "configuredState",
channelId: "env-chat",
cfg: {},
env,
}),
).toBe(configured);
});
it("prefers built bundled package-state probes when the catalog root is source", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-package-state-probe-"));
tempDirs.push(root);
+7 -1
View File
@@ -20,6 +20,7 @@ import {
getCachedPluginModuleLoader,
type PluginModuleLoaderCache,
} from "../../plugins/plugin-module-loader-cache.js";
import { isSafeChannelEnvVarTriggerName } from "../../secrets/channel-env-var-names.js";
import { loadChannelPluginModule, resolveExistingPluginModulePath } from "./module-loader.js";
type ChannelPackageStateChecker = (params: {
@@ -81,7 +82,12 @@ function loadChannelPackageStateModule(params: { modulePath: string; rootDir: st
}
function hasNonEmptyEnvValue(env: NodeJS.ProcessEnv | undefined, key: string): boolean {
return typeof env?.[key] === "string" && env[key].trim().length > 0;
if (!env || !isSafeChannelEnvVarTriggerName(key)) {
return false;
}
const normalized = key.trim();
const value = env[normalized] ?? env[normalized.toUpperCase()];
return typeof value === "string" && value.trim().length > 0;
}
function resolveSourceBundledPluginRoot(rootDir: string): {
+2 -2
View File
@@ -1,6 +1,6 @@
// Determines whether a channel is configured from bootstrap and plugin state.
import { getBootstrapChannelPlugin } from "../channels/plugins/bootstrap-registry.js";
import { hasBundledChannelConfiguredState } from "../channels/plugins/configured-state.js";
import { hasBundledChannelPackageState } from "../channels/plugins/package-state-probes.js";
import {
hasMeaningfulChannelConfigShallow,
resolveChannelConfigRecord,
@@ -19,7 +19,7 @@ export function isChannelConfigured(
return true;
}
// Bundled channels can expose configured state through env vars or persisted credential files.
if (hasBundledChannelConfiguredState({ channelId, cfg, env })) {
if (hasBundledChannelPackageState({ metadataKey: "configuredState", channelId, cfg, env })) {
return true;
}
// Bootstrap plugins cover channels that are available before full plugin registry loading.
+10 -8
View File
@@ -20,15 +20,17 @@ import {
import type { OpenClawConfig } from "./types.openclaw.js";
import { validateConfigObject } from "./validation.js";
vi.mock("../channels/plugins/configured-state.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../channels/plugins/configured-state.js")>();
vi.mock("../channels/plugins/package-state-probes.js", async (importOriginal) => {
const actual =
await importOriginal<typeof import("../channels/plugins/package-state-probes.js")>();
return {
...actual,
hasBundledChannelConfiguredState: (params: {
channelId: string;
cfg: OpenClawConfig;
env?: NodeJS.ProcessEnv;
}) => {
hasBundledChannelPackageState: (
params: Parameters<typeof actual.hasBundledChannelPackageState>[0],
) => {
if (params.metadataKey !== "configuredState") {
return actual.hasBundledChannelPackageState(params);
}
if (params.channelId === "cache-channel") {
return Boolean(params.env?.CACHE_CHANNEL_TOKEN?.trim());
}
@@ -40,7 +42,7 @@ vi.mock("../channels/plugins/configured-state.js", async (importOriginal) => {
Boolean(params.env?.[key]?.trim()),
);
}
return actual.hasBundledChannelConfiguredState(params);
return actual.hasBundledChannelPackageState(params);
},
};
});
+8 -5
View File
@@ -16,9 +16,9 @@ import {
type ChannelPresenceSignalSource,
} from "../channels/config-presence.js";
import {
hasBundledChannelConfiguredState,
listBundledChannelIdsWithConfiguredState,
} from "../channels/plugins/configured-state.js";
hasBundledChannelPackageState,
listBundledChannelIdsForPackageState,
} from "../channels/plugins/package-state-probes.js";
import { findChatChannelMeta, normalizeChatChannelId } from "../channels/registry.js";
import { isBlockedObjectKey } from "../infra/prototype-keys.js";
import { normalizePluginsConfig } from "../plugins/config-state.js";
@@ -299,7 +299,9 @@ function collectConfiguredChannelIds(
discovery?: PluginDiscoveryResult,
ambientEnvTriggers: AmbientEnvTriggerPolicy = "allow",
): string[] {
const configuredStateChannelIds = new Set(listBundledChannelIdsWithConfiguredState(discovery));
const configuredStateChannelIds = new Set(
listBundledChannelIdsForPackageState("configuredState", discovery),
);
return listPotentialConfiguredChannelPresenceSignals(cfg, env, {
includePersistedAuthState: false,
discovery,
@@ -333,7 +335,8 @@ function isAutoEnableConfiguredChannelSignal(params: {
if (
params.source === "env" &&
params.configuredStateChannelIds.has(params.channelId) &&
!hasBundledChannelConfiguredState({
!hasBundledChannelPackageState({
metadataKey: "configuredState",
channelId: params.channelId,
cfg: params.cfg,
env: params.env,
+14 -31
View File
@@ -12,7 +12,6 @@ import {
import { hasChannelPackageState } from "../channels/plugins/package-state-probes.js";
import { resolveConfigWidePluginManifestRegistry } from "../config/io.plugin-metadata.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { isSafeChannelEnvVarTriggerName } from "../secrets/channel-env-var-names.js";
import { resolveManifestActivationPluginIds } from "./activation-planner.js";
import {
createPluginActivationSource,
@@ -74,15 +73,6 @@ function normalizeChannelIds(channelIds: Iterable<string>): string[] {
);
}
function hasNonEmptyEnvValue(env: NodeJS.ProcessEnv, key: string): boolean {
if (!isSafeChannelEnvVarTriggerName(key)) {
return false;
}
const trimmed = key.trim();
const value = env[trimmed] ?? env[trimmed.toUpperCase()];
return typeof value === "string" && value.trim().length > 0;
}
/** True when config contains meaningful enabled channel settings. */
export function hasExplicitChannelConfig(params: {
config: OpenClawConfig;
@@ -175,27 +165,20 @@ function listManifestEnvConfiguredChannelSignals(params: {
continue;
}
contractChannelIds.add(normalizedChannelId);
if (hasModuleContract) {
if (
!params.envSignalChannelIds.has(normalizedChannelId) ||
!packageChannel ||
!hasChannelPackageState({
entry: {
pluginId: record.id,
origin: record.origin,
rootDir: record.rootDir,
channel: packageChannel,
},
metadataKey: "configuredState",
cfg: params.config,
env: params.env,
})
) {
continue;
}
} else if (
!allOf.every((envVar) => hasNonEmptyEnvValue(params.env, envVar)) ||
(anyOf.length > 0 && !anyOf.some((envVar) => hasNonEmptyEnvValue(params.env, envVar)))
if (
(hasModuleContract && !params.envSignalChannelIds.has(normalizedChannelId)) ||
!packageChannel ||
!hasChannelPackageState({
entry: {
pluginId: record.id,
origin: record.origin,
rootDir: record.rootDir,
channel: packageChannel,
},
metadataKey: "configuredState",
cfg: params.config,
env: params.env,
})
) {
continue;
}