refactor: dedupe channel lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 11:15:03 +01:00
parent 5de04bc1d5
commit 36938bccb5
14 changed files with 61 additions and 26 deletions

View File

@@ -1,3 +1,8 @@
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "../shared/string-coerce.js";
export type AllowlistMatchSource =
| "wildcard"
| "id"
@@ -37,7 +42,9 @@ export function compileAllowlist(entries: ReadonlyArray<string>): CompiledAllowl
function compileSimpleAllowlist(entries: ReadonlyArray<string | number>): CompiledAllowlist {
return compileAllowlist(
entries.map((entry) => String(entry).trim().toLowerCase()).filter(Boolean),
entries
.map((entry) => normalizeOptionalLowercaseString(String(entry)))
.filter((entry): entry is string => Boolean(entry)),
);
}
@@ -98,8 +105,8 @@ export function resolveAllowlistMatchSimple(params: {
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
}
const senderId = params.senderId.toLowerCase();
const senderName = params.senderName?.toLowerCase();
const senderId = normalizeLowercaseStringOrEmpty(params.senderId);
const senderName = normalizeOptionalLowercaseString(params.senderName);
return resolveAllowlistCandidates({
compiledAllowlist: allowFrom,
candidates: [

View File

@@ -1,5 +1,6 @@
import { mapAllowFromEntries } from "openclaw/plugin-sdk/channel-config-helpers";
import type { RuntimeEnv } from "../../runtime.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
import { summarizeStringEntries } from "../../shared/string-sample.js";
export type AllowlistUserResolutionLike = {
@@ -16,7 +17,7 @@ function dedupeAllowlistEntries(entries: string[]): string[] {
if (!normalized) {
continue;
}
const key = normalized.toLowerCase();
const key = normalizeLowercaseStringOrEmpty(normalized);
if (seen.has(key)) {
continue;
}

View File

@@ -1,3 +1,5 @@
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
export type ChannelMatchSource = "direct" | "parent" | "wildcard";
export type ChannelEntryMatch<T> = {
@@ -32,9 +34,7 @@ export function resolveChannelMatchConfig<
}
export function normalizeChannelSlug(value: string): string {
return value
.trim()
.toLowerCase()
return normalizeLowercaseStringOrEmpty(value)
.replace(/^#/, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");

View File

@@ -2,6 +2,7 @@ import type { OpenClawConfig } from "../config/config.js";
import { resolveConversationIdFromTargets } from "../infra/outbound/conversation-id.js";
import { getActivePluginChannelRegistry } from "../plugins/runtime.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "../shared/string-coerce.js";
@@ -78,7 +79,7 @@ function resolveChannelTargetId(params: {
return undefined;
}
const lower = target.toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(target);
const channelPrefix = `${params.channel}:`;
if (lower.startsWith(channelPrefix)) {
return resolveChannelTargetId({

View File

@@ -1,3 +1,5 @@
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
export type ResolveNativeCommandSessionTargetsParams = {
agentId: string;
sessionPrefix: string;
@@ -13,7 +15,9 @@ export function resolveNativeCommandSessionTargets(
const rawSessionKey =
params.boundSessionKey ?? `agent:${params.agentId}:${params.sessionPrefix}:${params.userId}`;
return {
sessionKey: params.lowercaseSessionKey ? rawSessionKey.toLowerCase() : rawSessionKey,
sessionKey: params.lowercaseSessionKey
? normalizeLowercaseStringOrEmpty(rawSessionKey)
: rawSessionKey,
commandTargetSessionKey: params.boundSessionKey ?? params.targetSessionKey,
};
}

View File

@@ -13,6 +13,10 @@ import {
resolveDefaultAgentId,
} from "../../agents/agent-scope.js";
import type { OpenClawConfig } from "../../config/config.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "../../shared/string-coerce.js";
import type {
ConfiguredBindingRuleConfig,
ConfiguredBindingTargetFactory,
@@ -26,8 +30,9 @@ function resolveAgentRuntimeAcpDefaults(params: { cfg: OpenClawConfig; ownerAgen
cwd?: string;
backend?: string;
} {
const ownerAgentId = normalizeLowercaseStringOrEmpty(params.ownerAgentId);
const agent = params.cfg.agents?.list?.find(
(entry) => entry.id?.trim().toLowerCase() === params.ownerAgentId.toLowerCase(),
(entry) => normalizeOptionalLowercaseString(entry.id) === ownerAgentId,
);
if (!agent || agent.runtime?.type !== "acp") {
return {};

View File

@@ -1,4 +1,5 @@
import type { OpenClawConfig } from "../../config/config.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
import { isInternalMessageChannel } from "../../utils/message-channel.js";
import {
authorizeConfigWriteShared,
@@ -40,7 +41,7 @@ export function resolveExplicitConfigWriteTarget(scope: ConfigWriteScope): Confi
export function resolveConfigWriteTargetFromPath(path: string[]): ConfigWriteTarget {
return resolveConfigWriteTargetFromPathShared({
path,
normalizeChannelId: (raw) => raw.trim().toLowerCase() as ChannelId,
normalizeChannelId: (raw) => normalizeLowercaseStringOrEmpty(raw) as ChannelId,
});
}

View File

@@ -5,7 +5,10 @@ import {
type ParsedThreadSessionSuffix,
type RawSessionConversationRef,
} from "../../sessions/session-key-utils.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import {
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "../../shared/string-coerce.js";
import { normalizeChannelId as normalizeChatChannelId } from "../registry.js";
import { getLoadedChannelPlugin, normalizeChannelId as normalizeAnyChannelId } from "./registry.js";
@@ -55,7 +58,8 @@ function normalizeResolvedChannel(channel: string): string {
return (
normalizeAnyChannelId(channel) ??
normalizeChatChannelId(channel) ??
channel.trim().toLowerCase()
normalizeOptionalLowercaseString(channel) ??
""
);
}

View File

@@ -1,3 +1,5 @@
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
export type { DirectoryConfigParams } from "./plugins/directory-types.js";
export type { ChannelDirectoryEntry } from "./plugins/types.js";
@@ -16,7 +18,7 @@ export type MessagingTargetParseOptions = {
};
export function normalizeTargetId(kind: MessagingTargetKind, id: string): string {
return `${kind}:${id}`.toLowerCase();
return normalizeLowercaseStringOrEmpty(`${kind}:${id}`);
}
export function buildMessagingTarget(

View File

@@ -1,5 +1,6 @@
import type { OpenClawConfig } from "../config/config.js";
import { normalizeAccountId } from "../routing/session-key.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { getChannelPlugin } from "./plugins/index.js";
const DEFAULT_THREAD_BINDING_IDLE_HOURS = 24;
@@ -28,9 +29,7 @@ export type ThreadBindingSpawnPolicy = {
};
function normalizeChannelId(value: string | undefined | null): string {
return String(value ?? "")
.trim()
.toLowerCase();
return normalizeLowercaseStringOrEmpty(value);
}
export function supportsAutomaticThreadBindingSpawn(channel: string): boolean {

View File

@@ -8,6 +8,7 @@ import type { ChannelId, ChannelPlugin, ChannelSetupInput } from "../../channels
import { replaceConfigFile, type OpenClawConfig } from "../../config/config.js";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
import { createClackPrompter } from "../../wizard/clack-prompter.js";
import { applyAgentBindings, describeBinding } from "../agents.bindings.js";
import { isCatalogChannelInstalled } from "../channel-setup/discovery.js";
@@ -28,16 +29,18 @@ export type ChannelsAddOptions = {
} & Omit<ChannelSetupInput, "groupChannels" | "dmAllowlist" | "initialSyncLimit">;
function resolveCatalogChannelEntry(raw: string, cfg: OpenClawConfig | null) {
const trimmed = raw.trim().toLowerCase();
const trimmed = normalizeOptionalLowercaseString(raw);
if (!trimmed) {
return undefined;
}
const workspaceDir = cfg ? resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)) : undefined;
return listChannelPluginCatalogEntries({ workspaceDir }).find((entry) => {
if (entry.id.toLowerCase() === trimmed) {
if (normalizeOptionalLowercaseString(entry.id) === trimmed) {
return true;
}
return (entry.meta.aliases ?? []).some((alias) => alias.trim().toLowerCase() === trimmed);
return (entry.meta.aliases ?? []).some(
(alias) => normalizeOptionalLowercaseString(alias) === trimmed,
);
});
}

View File

@@ -18,7 +18,10 @@ import {
import { danger } from "../../globals.js";
import { formatErrorMessage } from "../../infra/errors.js";
import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../../shared/string-coerce.js";
import { theme } from "../../terminal/theme.js";
import { resolveInstallableChannelPlugin } from "../channel-setup/channel-plugin-resolution.js";
import { formatChannelAccountLabel, requireValidConfig } from "./shared.js";
@@ -220,7 +223,7 @@ export async function channelsCapabilitiesCommand(
}
let cfg = loadedCfg;
const timeoutMs = normalizeTimeout(opts.timeout, 10_000);
const rawChannel = typeof opts.channel === "string" ? opts.channel.trim().toLowerCase() : "";
const rawChannel = normalizeLowercaseStringOrEmpty(opts.channel);
const rawTarget = typeof opts.target === "string" ? opts.target.trim() : "";
if (opts.account && (!rawChannel || rawChannel === "all")) {

View File

@@ -3,6 +3,7 @@ import { listChannelPlugins } from "../../channels/plugins/index.js";
import { getResolvedLoggerSettings } from "../../logging.js";
import { parseLogLine } from "../../logging/parse-log-line.js";
import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
import { theme } from "../../terminal/theme.js";
export type ChannelsLogsOptions = {
@@ -20,7 +21,7 @@ const getChannelSet = () =>
new Set<string>([...listChannelPlugins().map((plugin) => plugin.id), "all"]);
function parseChannelFilter(raw?: string) {
const trimmed = raw?.trim().toLowerCase();
const trimmed = normalizeLowercaseStringOrEmpty(raw);
if (!trimmed) {
return "all";
}

View File

@@ -6,6 +6,10 @@ import { loadConfig, readConfigFileSnapshot, replaceConfigFile } from "../../con
import { danger } from "../../globals.js";
import { resolveMessageChannelSelection } from "../../infra/outbound/channel-selection.js";
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "../../shared/string-coerce.js";
import { resolveInstallableChannelPlugin } from "../channel-setup/channel-plugin-resolution.js";
export type ChannelsResolveOptions = {
@@ -71,10 +75,10 @@ function detectAutoKindForPlugin(
return generic;
}
const trimmed = input.trim();
const lowered = trimmed.toLowerCase();
const lowered = normalizeLowercaseStringOrEmpty(trimmed);
const prefixes = [plugin.id, ...(plugin.meta?.aliases ?? [])]
.map((entry) => entry.trim().toLowerCase())
.filter(Boolean);
.map((entry) => normalizeOptionalLowercaseString(entry))
.filter((entry): entry is string => Boolean(entry));
for (const prefix of prefixes) {
if (!lowered.startsWith(`${prefix}:`)) {
continue;