From d228a6214329aa94268a7ccacff64f3c5aa7a30e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 7 Mar 2026 23:11:04 +0000 Subject: [PATCH] refactor: share trimmed string entry normalization --- src/agents/auth-profiles/profiles.ts | 5 ++--- src/agents/skills/config.ts | 3 ++- src/agents/skills/filter.ts | 4 +++- src/auto-reply/command-auth.ts | 3 ++- src/auto-reply/reply/commands-allowlist.ts | 3 ++- src/auto-reply/reply/get-reply.ts | 3 ++- src/auto-reply/reply/reply-elevated.ts | 5 +++-- src/channels/account-summary.ts | 3 ++- src/commands/models/auth-order.ts | 3 ++- src/plugin-sdk/channel-config-helpers.ts | 3 ++- src/shared/string-normalization.test.ts | 5 +++++ src/shared/string-normalization.ts | 4 ++-- 12 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/agents/auth-profiles/profiles.ts b/src/agents/auth-profiles/profiles.ts index edd51fdb5..f05808429 100644 --- a/src/agents/auth-profiles/profiles.ts +++ b/src/agents/auth-profiles/profiles.ts @@ -1,3 +1,4 @@ +import { normalizeStringEntries } from "../../shared/string-normalization.js"; import { normalizeSecretInput } from "../../utils/normalize-secret-input.js"; import { normalizeProviderId, normalizeProviderIdForAuth } from "../model-selection.js"; import { @@ -18,9 +19,7 @@ export async function setAuthProfileOrder(params: { }): Promise { const providerKey = normalizeProviderId(params.provider); const sanitized = - params.order && Array.isArray(params.order) - ? params.order.map((entry) => String(entry).trim()).filter(Boolean) - : []; + params.order && Array.isArray(params.order) ? normalizeStringEntries(params.order) : []; const deduped = dedupeProfileIds(sanitized); return await updateAuthProfileStoreWithLock({ diff --git a/src/agents/skills/config.ts b/src/agents/skills/config.ts index b210efc9e..2dfe78acd 100644 --- a/src/agents/skills/config.ts +++ b/src/agents/skills/config.ts @@ -6,6 +6,7 @@ import { resolveConfigPath, resolveRuntimePlatform, } from "../../shared/config-eval.js"; +import { normalizeStringEntries } from "../../shared/string-normalization.js"; import { resolveSkillKey } from "./frontmatter.js"; import type { SkillEligibilityContext, SkillEntry } from "./types.js"; @@ -42,7 +43,7 @@ function normalizeAllowlist(input: unknown): string[] | undefined { if (!Array.isArray(input)) { return undefined; } - const normalized = input.map((entry) => String(entry).trim()).filter(Boolean); + const normalized = normalizeStringEntries(input); return normalized.length > 0 ? normalized : undefined; } diff --git a/src/agents/skills/filter.ts b/src/agents/skills/filter.ts index a5fb82228..27496737b 100644 --- a/src/agents/skills/filter.ts +++ b/src/agents/skills/filter.ts @@ -1,8 +1,10 @@ +import { normalizeStringEntries } from "../../shared/string-normalization.js"; + export function normalizeSkillFilter(skillFilter?: ReadonlyArray): string[] | undefined { if (skillFilter === undefined) { return undefined; } - return skillFilter.map((entry) => String(entry).trim()).filter(Boolean); + return normalizeStringEntries(skillFilter); } export function normalizeSkillFilterForComparison( diff --git a/src/auto-reply/command-auth.ts b/src/auto-reply/command-auth.ts index 583340c93..ead6e6e03 100644 --- a/src/auto-reply/command-auth.ts +++ b/src/auto-reply/command-auth.ts @@ -3,6 +3,7 @@ import { getChannelDock, listChannelDocks } from "../channels/dock.js"; import type { ChannelId } from "../channels/plugins/types.js"; import { normalizeAnyChannelId } from "../channels/registry.js"; import type { OpenClawConfig } from "../config/config.js"; +import { normalizeStringEntries } from "../shared/string-normalization.js"; import { INTERNAL_MESSAGE_CHANNEL, isInternalMessageChannel, @@ -85,7 +86,7 @@ function formatAllowFromList(params: { if (dock?.config?.formatAllowFrom) { return dock.config.formatAllowFrom({ cfg, accountId, allowFrom }); } - return allowFrom.map((entry) => String(entry).trim()).filter(Boolean); + return normalizeStringEntries(allowFrom); } function normalizeAllowFromEntry(params: { diff --git a/src/auto-reply/reply/commands-allowlist.ts b/src/auto-reply/reply/commands-allowlist.ts index 13c79dc79..766bb5f41 100644 --- a/src/auto-reply/reply/commands-allowlist.ts +++ b/src/auto-reply/reply/commands-allowlist.ts @@ -23,6 +23,7 @@ import { normalizeAccountId, normalizeOptionalAccountId, } from "../../routing/session-key.js"; +import { normalizeStringEntries } from "../../shared/string-normalization.js"; import { resolveSignalAccount } from "../../signal/accounts.js"; import { resolveSlackAccount } from "../../slack/accounts.js"; import { resolveSlackUserAllowlist } from "../../slack/resolve-users.js"; @@ -165,7 +166,7 @@ function normalizeAllowFrom(params: { allowFrom: params.values, }); } - return params.values.map((entry) => String(entry).trim()).filter(Boolean); + return normalizeStringEntries(params.values); } function formatEntryList(entries: string[], resolved?: Map): string { diff --git a/src/auto-reply/reply/get-reply.ts b/src/auto-reply/reply/get-reply.ts index 911cddf46..be4c8d362 100644 --- a/src/auto-reply/reply/get-reply.ts +++ b/src/auto-reply/reply/get-reply.ts @@ -12,6 +12,7 @@ import { type OpenClawConfig, loadConfig } from "../../config/config.js"; import { applyLinkUnderstanding } from "../../link-understanding/apply.js"; import { applyMediaUnderstanding } from "../../media-understanding/apply.js"; import { defaultRuntime } from "../../runtime.js"; +import { normalizeStringEntries } from "../../shared/string-normalization.js"; import { resolveCommandAuthorization } from "../command-auth.js"; import type { MsgContext } from "../templating.js"; import { SILENT_REPLY_TOKEN } from "../tokens.js"; @@ -33,7 +34,7 @@ function mergeSkillFilters(channelFilter?: string[], agentFilter?: string[]): st if (!Array.isArray(list)) { return undefined; } - return list.map((entry) => String(entry).trim()).filter(Boolean); + return normalizeStringEntries(list); }; const channel = normalize(channelFilter); const agent = normalize(agentFilter); diff --git a/src/auto-reply/reply/reply-elevated.ts b/src/auto-reply/reply/reply-elevated.ts index 1adfbc055..17da0058d 100644 --- a/src/auto-reply/reply/reply-elevated.ts +++ b/src/auto-reply/reply/reply-elevated.ts @@ -2,6 +2,7 @@ import { resolveAgentConfig } from "../../agents/agent-scope.js"; import { getChannelDock } from "../../channels/dock.js"; import { normalizeChannelId } from "../../channels/plugins/index.js"; import type { AgentElevatedAllowFromConfig, OpenClawConfig } from "../../config/config.js"; +import { normalizeStringEntries } from "../../shared/string-normalization.js"; import type { MsgContext } from "../templating.js"; import { type AllowFromFormatter, @@ -36,7 +37,7 @@ function resolveAllowFromFormatter(params: { const dock = normalizedProvider ? getChannelDock(normalizedProvider) : undefined; const formatAllowFrom = dock?.config?.formatAllowFrom; if (!formatAllowFrom) { - return (values) => values.map((entry) => String(entry).trim()).filter(Boolean); + return (values) => normalizeStringEntries(values); } return (values) => formatAllowFrom({ @@ -64,7 +65,7 @@ function isApprovedElevatedSender(params: { return false; } - const allowTokens = rawAllow.map((entry) => String(entry).trim()).filter(Boolean); + const allowTokens = normalizeStringEntries(rawAllow); if (allowTokens.length === 0) { return false; } diff --git a/src/channels/account-summary.ts b/src/channels/account-summary.ts index a36a45d67..4ecf28685 100644 --- a/src/channels/account-summary.ts +++ b/src/channels/account-summary.ts @@ -1,4 +1,5 @@ import type { OpenClawConfig } from "../config/config.js"; +import { normalizeStringEntries } from "../shared/string-normalization.js"; import { projectSafeChannelAccountSnapshotFields } from "./account-snapshot-fields.js"; import type { ChannelAccountSnapshot } from "./plugins/types.core.js"; import type { ChannelPlugin } from "./plugins/types.plugin.js"; @@ -34,7 +35,7 @@ export function formatChannelAllowFrom(params: { allowFrom: params.allowFrom, }); } - return params.allowFrom.map((entry) => String(entry).trim()).filter(Boolean); + return normalizeStringEntries(params.allowFrom); } function asRecord(value: unknown): Record | undefined { diff --git a/src/commands/models/auth-order.ts b/src/commands/models/auth-order.ts index a177b1a8a..e8c374ece 100644 --- a/src/commands/models/auth-order.ts +++ b/src/commands/models/auth-order.ts @@ -6,6 +6,7 @@ import { } from "../../agents/auth-profiles.js"; import { normalizeProviderId } from "../../agents/model-selection.js"; import type { RuntimeEnv } from "../../runtime.js"; +import { normalizeStringEntries } from "../../shared/string-normalization.js"; import { shortenHomePath } from "../../utils.js"; import { loadModelsConfig } from "./load-config.js"; import { resolveKnownAgentId } from "./shared.js"; @@ -104,7 +105,7 @@ export async function modelsAuthOrderSetCommand( allowKeychainPrompt: false, }); const providerKey = provider; - const requested = (opts.order ?? []).map((entry) => String(entry).trim()).filter(Boolean); + const requested = normalizeStringEntries(opts.order ?? []); if (requested.length === 0) { throw new Error("Missing profile ids. Provide one or more profile ids."); } diff --git a/src/plugin-sdk/channel-config-helpers.ts b/src/plugin-sdk/channel-config-helpers.ts index 24051922e..d5baa765d 100644 --- a/src/plugin-sdk/channel-config-helpers.ts +++ b/src/plugin-sdk/channel-config-helpers.ts @@ -2,6 +2,7 @@ import { normalizeWhatsAppAllowFromEntries } from "../channels/plugins/normalize import type { OpenClawConfig } from "../config/config.js"; import { resolveIMessageAccount } from "../imessage/accounts.js"; import { normalizeAccountId } from "../routing/session-key.js"; +import { normalizeStringEntries } from "../shared/string-normalization.js"; import { resolveWhatsAppAccount } from "../web/accounts.js"; export function mapAllowFromEntries( @@ -11,7 +12,7 @@ export function mapAllowFromEntries( } export function formatTrimmedAllowFromEntries(allowFrom: Array): string[] { - return allowFrom.map((entry) => String(entry).trim()).filter(Boolean); + return normalizeStringEntries(allowFrom); } export function resolveOptionalConfigString( diff --git a/src/shared/string-normalization.test.ts b/src/shared/string-normalization.test.ts index 15e5ee5fc..ca92a8ae8 100644 --- a/src/shared/string-normalization.test.ts +++ b/src/shared/string-normalization.test.ts @@ -9,6 +9,11 @@ import { describe("shared/string-normalization", () => { it("normalizes mixed allow-list entries", () => { expect(normalizeStringEntries([" a ", 42, "", " ", "z"])).toEqual(["a", "42", "z"]); + expect(normalizeStringEntries([" ok ", null, { toString: () => " obj " }])).toEqual([ + "ok", + "null", + "obj", + ]); expect(normalizeStringEntries(undefined)).toEqual([]); }); diff --git a/src/shared/string-normalization.ts b/src/shared/string-normalization.ts index 67a191a8b..2c117390b 100644 --- a/src/shared/string-normalization.ts +++ b/src/shared/string-normalization.ts @@ -1,8 +1,8 @@ -export function normalizeStringEntries(list?: Array) { +export function normalizeStringEntries(list?: ReadonlyArray) { return (list ?? []).map((entry) => String(entry).trim()).filter(Boolean); } -export function normalizeStringEntriesLower(list?: Array) { +export function normalizeStringEntriesLower(list?: ReadonlyArray) { return normalizeStringEntries(list).map((entry) => entry.toLowerCase()); }