From a5ff85f01c23765fff35762389e708e3f6bcec3e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 7 Apr 2026 08:15:36 +0100 Subject: [PATCH] refactor: dedupe lowercased readers --- extensions/diffs/src/language-hints.ts | 3 ++- extensions/diffs/src/tool.ts | 2 +- extensions/googlechat/src/monitor.ts | 3 ++- extensions/telegram/src/status-reaction-variants.ts | 3 ++- src/config/group-policy.ts | 5 +++-- src/config/sessions/types.ts | 3 ++- src/infra/exec-approvals-allowlist.ts | 5 +++-- src/infra/exec-approvals.ts | 10 +++++----- src/media-understanding/scope.ts | 5 +++-- src/plugins/command-registration.ts | 3 ++- src/realtime-transcription/provider-registry.ts | 8 ++------ src/tasks/task-registry.ts | 2 +- 12 files changed, 28 insertions(+), 24 deletions(-) diff --git a/extensions/diffs/src/language-hints.ts b/extensions/diffs/src/language-hints.ts index 558718ae0c0..a8bde8cd4e4 100644 --- a/extensions/diffs/src/language-hints.ts +++ b/extensions/diffs/src/language-hints.ts @@ -1,5 +1,6 @@ import { resolveLanguage } from "@pierre/diffs"; import type { FileContents, FileDiffMetadata, SupportedLanguages } from "@pierre/diffs"; +import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; import type { DiffViewerPayload } from "./types.js"; const PASSTHROUGH_LANGUAGE_HINTS = new Set(["ansi", "text"]); @@ -8,7 +9,7 @@ type DiffPayloadFile = FileContents | FileDiffMetadata; export async function normalizeSupportedLanguageHint( value?: string, ): Promise { - const normalized = value?.trim(); + const normalized = normalizeOptionalString(value); if (!normalized) { return undefined; } diff --git a/extensions/diffs/src/tool.ts b/extensions/diffs/src/tool.ts index 30ee5b48738..0851a292b9d 100644 --- a/extensions/diffs/src/tool.ts +++ b/extensions/diffs/src/tool.ts @@ -440,7 +440,7 @@ function buildArtifactContext( } function normalizeContextString(value: string | undefined): string | undefined { - const normalized = value?.trim(); + const normalized = normalizeOptionalString(value); return normalized ? normalized : undefined; } diff --git a/extensions/googlechat/src/monitor.ts b/extensions/googlechat/src/monitor.ts index 49621420e13..1071716fbb3 100644 --- a/extensions/googlechat/src/monitor.ts +++ b/extensions/googlechat/src/monitor.ts @@ -3,6 +3,7 @@ import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts, } from "openclaw/plugin-sdk/reply-payload"; +import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; import type { OpenClawConfig } from "../runtime-api.js"; import { createChannelReplyPipeline, @@ -72,7 +73,7 @@ export function registerGoogleChatWebhookTarget(target: WebhookTarget): () => vo } function normalizeAudienceType(value?: string | null): GoogleChatAudienceType | undefined { - const normalized = value?.trim().toLowerCase(); + const normalized = normalizeOptionalString(value)?.toLowerCase(); if (normalized === "app-url" || normalized === "app_url" || normalized === "app") { return "app-url"; } diff --git a/extensions/telegram/src/status-reaction-variants.ts b/extensions/telegram/src/status-reaction-variants.ts index 8c2b0de527a..137e7b2eeda 100644 --- a/extensions/telegram/src/status-reaction-variants.ts +++ b/extensions/telegram/src/status-reaction-variants.ts @@ -1,5 +1,6 @@ import type { ReactionTypeEmoji } from "@grammyjs/types"; import { DEFAULT_EMOJIS, type StatusReactionEmojis } from "openclaw/plugin-sdk/channel-feedback"; +import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; import type { TelegramChatDetails, TelegramGetChat } from "./bot/types.js"; type StatusReactionEmojiKey = keyof Required; @@ -114,7 +115,7 @@ const STATUS_REACTION_EMOJI_KEYS: StatusReactionEmojiKey[] = [ ]; function normalizeEmoji(value: string | undefined): string | undefined { - const trimmed = value?.trim(); + const trimmed = normalizeOptionalString(value); return trimmed ? trimmed : undefined; } diff --git a/src/config/group-policy.ts b/src/config/group-policy.ts index c2e7fe3f4df..61909994053 100644 --- a/src/config/group-policy.ts +++ b/src/config/group-policy.ts @@ -1,6 +1,7 @@ import type { ChannelId } from "../channels/plugins/types.js"; import { resolveAccountEntry } from "../routing/account-lookup.js"; import { normalizeAccountId } from "../routing/session-key.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import type { OpenClawConfig } from "./config.js"; import { parseToolsBySenderTypedKey, @@ -207,7 +208,7 @@ function resolveCompiledToolsBySenderPolicy( } function normalizeCandidate(value: string | null | undefined, type: SenderKeyType): string { - const trimmed = value?.trim(); + const trimmed = normalizeOptionalString(value); if (!trimmed) { return ""; } @@ -215,7 +216,7 @@ function normalizeCandidate(value: string | null | undefined, type: SenderKeyTyp } function normalizeSenderIdCandidates(value: string | null | undefined): string[] { - const trimmed = value?.trim(); + const trimmed = normalizeOptionalString(value); if (!trimmed) { return []; } diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index c57d31ec8ed..719b9c90ee7 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -2,6 +2,7 @@ import crypto from "node:crypto"; import type { Skill } from "@mariozechner/pi-coding-agent"; import type { ChatType } from "../../channels/chat-type.js"; import type { ChannelId } from "../../channels/plugins/types.js"; +import { normalizeOptionalString } from "../../shared/string-coerce.js"; import type { DeliveryContext } from "../../utils/delivery-context.js"; import type { TtsAutoMode } from "../types.tts.js"; @@ -235,7 +236,7 @@ export type SessionEntry = { }; function normalizeRuntimeField(value: string | undefined): string | undefined { - const trimmed = value?.trim(); + const trimmed = normalizeOptionalString(value); return trimmed ? trimmed : undefined; } diff --git a/src/infra/exec-approvals-allowlist.ts b/src/infra/exec-approvals-allowlist.ts index 538900a5c00..25731f1c94d 100644 --- a/src/infra/exec-approvals-allowlist.ts +++ b/src/infra/exec-approvals-allowlist.ts @@ -1,4 +1,5 @@ import path from "node:path"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import { isDispatchWrapperExecutable } from "./dispatch-wrapper-resolution.js"; import { analyzeShellCommand, @@ -149,12 +150,12 @@ function pickExecAllowlistContext(params: ExecAllowlistContext): ExecAllowlistCo } function normalizeSkillBinName(value: string | undefined): string | null { - const trimmed = value?.trim().toLowerCase(); + const trimmed = normalizeOptionalString(value)?.toLowerCase(); return trimmed && trimmed.length > 0 ? trimmed : null; } function normalizeSkillBinResolvedPath(value: string | undefined): string | null { - const trimmed = value?.trim(); + const trimmed = normalizeOptionalString(value); if (!trimmed) { return null; } diff --git a/src/infra/exec-approvals.ts b/src/infra/exec-approvals.ts index c8bc47a6bf6..fdfd6a58a69 100644 --- a/src/infra/exec-approvals.ts +++ b/src/infra/exec-approvals.ts @@ -16,7 +16,7 @@ export type ExecSecurity = "deny" | "allowlist" | "full"; export type ExecAsk = "off" | "on-miss" | "always"; export function normalizeExecHost(value?: string | null): ExecHost | null { - const normalized = value?.trim().toLowerCase(); + const normalized = normalizeOptionalString(value)?.toLowerCase(); if (normalized === "sandbox" || normalized === "gateway" || normalized === "node") { return normalized; } @@ -24,7 +24,7 @@ export function normalizeExecHost(value?: string | null): ExecHost | null { } export function normalizeExecTarget(value?: string | null): ExecTarget | null { - const normalized = value?.trim().toLowerCase(); + const normalized = normalizeOptionalString(value)?.toLowerCase(); if (normalized === "auto") { return normalized; } @@ -35,7 +35,7 @@ export function normalizeExecTarget(value?: string | null): ExecTarget | null { const toStringOrUndefined = readStringValue; export function normalizeExecSecurity(value?: string | null): ExecSecurity | null { - const normalized = value?.trim().toLowerCase(); + const normalized = normalizeOptionalString(value)?.toLowerCase(); if (normalized === "deny" || normalized === "allowlist" || normalized === "full") { return normalized; } @@ -43,7 +43,7 @@ export function normalizeExecSecurity(value?: string | null): ExecSecurity | nul } export function normalizeExecAsk(value?: string | null): ExecAsk | null { - const normalized = value?.trim().toLowerCase(); + const normalized = normalizeOptionalString(value)?.toLowerCase(); if (normalized === "off" || normalized === "on-miss" || normalized === "always") { return normalized; } @@ -193,7 +193,7 @@ export function resolveExecApprovalsSocketPath(): string { } function normalizeAllowlistPattern(value: string | undefined): string | null { - const trimmed = value?.trim() ?? ""; + const trimmed = normalizeOptionalString(value) ?? ""; return trimmed ? trimmed.toLowerCase() : null; } diff --git a/src/media-understanding/scope.ts b/src/media-understanding/scope.ts index a31031c02d4..7a68c901a6c 100644 --- a/src/media-understanding/scope.ts +++ b/src/media-understanding/scope.ts @@ -1,10 +1,11 @@ import { normalizeChatType } from "../channels/chat-type.js"; import type { MediaUnderstandingScopeConfig } from "../config/types.tools.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; export type MediaUnderstandingScopeDecision = "allow" | "deny"; function normalizeDecision(value?: string | null): MediaUnderstandingScopeDecision | undefined { - const normalized = value?.trim().toLowerCase(); + const normalized = normalizeOptionalString(value)?.toLowerCase(); if (normalized === "allow") { return "allow"; } @@ -15,7 +16,7 @@ function normalizeDecision(value?: string | null): MediaUnderstandingScopeDecisi } function normalizeMatch(value?: string | null): string | undefined { - const normalized = value?.trim().toLowerCase(); + const normalized = normalizeOptionalString(value)?.toLowerCase(); return normalized || undefined; } diff --git a/src/plugins/command-registration.ts b/src/plugins/command-registration.ts index 92e61a9df79..2600afdee64 100644 --- a/src/plugins/command-registration.ts +++ b/src/plugins/command-registration.ts @@ -1,4 +1,5 @@ import { logVerbose } from "../globals.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import { clearPluginCommands, clearPluginCommandsForPlugin, @@ -125,7 +126,7 @@ export function validatePluginCommandDefinition( export function listPluginInvocationKeys(command: OpenClawPluginCommandDefinition): string[] { const keys = new Set(); const push = (value: string | undefined) => { - const normalized = value?.trim().toLowerCase(); + const normalized = normalizeOptionalString(value)?.toLowerCase(); if (!normalized) { return; } diff --git a/src/realtime-transcription/provider-registry.ts b/src/realtime-transcription/provider-registry.ts index 28d2e3125ed..0f66570b40b 100644 --- a/src/realtime-transcription/provider-registry.ts +++ b/src/realtime-transcription/provider-registry.ts @@ -1,17 +1,13 @@ import type { OpenClawConfig } from "../config/config.js"; import { resolvePluginCapabilityProviders } from "../plugins/capability-provider-runtime.js"; import type { RealtimeTranscriptionProviderPlugin } from "../plugins/types.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import type { RealtimeTranscriptionProviderId } from "./provider-types.js"; -function trimToUndefined(value: string | undefined): string | undefined { - const trimmed = value?.trim().toLowerCase(); - return trimmed ? trimmed : undefined; -} - export function normalizeRealtimeTranscriptionProviderId( providerId: string | undefined, ): RealtimeTranscriptionProviderId | undefined { - return trimToUndefined(providerId); + return normalizeOptionalString(providerId)?.toLowerCase(); } function resolveRealtimeTranscriptionProviderEntries( diff --git a/src/tasks/task-registry.ts b/src/tasks/task-registry.ts index cbc58d3d920..b8b8f4af5ee 100644 --- a/src/tasks/task-registry.ts +++ b/src/tasks/task-registry.ts @@ -592,7 +592,7 @@ function pickPreferredRunIdTask(matches: TaskRecord[]): TaskRecord | undefined { } function normalizeComparableText(value: string | undefined): string { - return value?.trim() ?? ""; + return normalizeOptionalString(value) ?? ""; } function compareTasksNewestFirst(