diff --git a/extensions/device-pair/index.ts b/extensions/device-pair/index.ts index 21bbdef8bac..3754b570c7f 100644 --- a/extensions/device-pair/index.ts +++ b/extensions/device-pair/index.ts @@ -1,7 +1,10 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalString, +} from "openclaw/plugin-sdk/text-runtime"; import { clearDeviceBootstrapTokens, definePluginEntry, @@ -563,7 +566,7 @@ export default definePluginEntry({ handler: async (ctx) => { const args = normalizeOptionalString(ctx.args) ?? ""; const tokens = args.split(/\s+/).filter(Boolean); - const action = tokens[0]?.toLowerCase() ?? ""; + const action = normalizeLowercaseStringOrEmpty(tokens[0]); const gatewayClientScopes = Array.isArray(ctx.gatewayClientScopes) ? ctx.gatewayClientScopes : undefined; @@ -583,7 +586,7 @@ export default definePluginEntry({ } if (action === "notify") { - const notifyAction = normalizeOptionalString(tokens[1])?.toLowerCase() ?? "status"; + const notifyAction = normalizeLowercaseStringOrEmpty(tokens[1]) || "status"; return await handleNotifyCommand({ api, ctx, diff --git a/extensions/device-pair/pair-command-approve.ts b/extensions/device-pair/pair-command-approve.ts index e68b86e066b..55b9e5d36ca 100644 --- a/extensions/device-pair/pair-command-approve.ts +++ b/extensions/device-pair/pair-command-approve.ts @@ -1,4 +1,7 @@ -import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalString, +} from "openclaw/plugin-sdk/text-runtime"; import { approveDevicePairing, listDevicePairing } from "./api.js"; import { formatPendingRequests } from "./notify.js"; @@ -31,7 +34,7 @@ export function selectPendingApprovalRequest(params: { : { reply: buildMultiplePendingApprovalReply(params.pending) }; } - if (params.requested.toLowerCase() === "latest") { + if (normalizeLowercaseStringOrEmpty(params.requested) === "latest") { return { pending: [...params.pending].toSorted((a, b) => (b.ts ?? 0) - (a.ts ?? 0))[0], }; diff --git a/extensions/tlon/src/monitor/approval.ts b/extensions/tlon/src/monitor/approval.ts index d9dfcb053f1..882c8704d60 100644 --- a/extensions/tlon/src/monitor/approval.ts +++ b/extensions/tlon/src/monitor/approval.ts @@ -7,6 +7,7 @@ // Extensions cannot import core internals directly, so use node:crypto here. import { randomBytes } from "node:crypto"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import type { PendingApproval } from "../settings.js"; export type { PendingApproval }; @@ -106,7 +107,7 @@ export type ApprovalResponse = { * - "block" permanently blocks the ship via Tlon's native blocking */ export function parseApprovalResponse(text: string): ApprovalResponse | null { - const trimmed = text.trim().toLowerCase(); + const trimmed = normalizeLowercaseStringOrEmpty(text); // Match "approve", "deny", or "block" optionally followed by an ID const match = trimmed.match(/^(approve|deny|block)(?:\s+(.+))?$/); @@ -125,7 +126,7 @@ export function parseApprovalResponse(text: string): ApprovalResponse | null { * Used to determine if we should intercept the message before normal processing. */ export function isApprovalResponse(text: string): boolean { - const trimmed = text.trim().toLowerCase(); + const trimmed = normalizeLowercaseStringOrEmpty(text); return trimmed.startsWith("approve") || trimmed.startsWith("deny") || trimmed.startsWith("block"); } diff --git a/extensions/tlon/src/monitor/media.ts b/extensions/tlon/src/monitor/media.ts index 0035760e409..3813187060c 100644 --- a/extensions/tlon/src/monitor/media.ts +++ b/extensions/tlon/src/monitor/media.ts @@ -7,6 +7,7 @@ import { MAX_IMAGE_BYTES, saveMediaBuffer, } from "openclaw/plugin-sdk/media-runtime"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { getDefaultSsrFPolicy } from "../urbit/context.js"; const MAX_IMAGES_PER_MESSAGE = 8; @@ -136,7 +137,7 @@ function getExtensionFromUrl(url: string): string | null { try { const pathname = new URL(url).pathname; const match = pathname.match(/\.([a-z0-9]+)$/i); - return match ? match[1].toLowerCase() : null; + return match ? normalizeLowercaseStringOrEmpty(match[1]) : null; } catch { return null; } diff --git a/extensions/tlon/src/tlon-api.ts b/extensions/tlon/src/tlon-api.ts index 911ea6264bb..01ba8718f4d 100644 --- a/extensions/tlon/src/tlon-api.ts +++ b/extensions/tlon/src/tlon-api.ts @@ -1,6 +1,7 @@ import crypto from "node:crypto"; import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import { authenticate } from "./urbit/auth.js"; import { scryUrbitPath } from "./urbit/channel-ops.js"; import { ssrfPolicyFromDangerouslyAllowPrivateNetwork } from "./urbit/context.js"; @@ -72,7 +73,7 @@ function getExtensionFromMimeType(mimeType?: string): string { if (!mimeType) { return ".jpg"; } - return mimeToExt[mimeType.toLowerCase()] || ".jpg"; + return mimeToExt[normalizeLowercaseStringOrEmpty(mimeType)] || ".jpg"; } function hasCustomS3Creds( diff --git a/extensions/voice-call/src/providers/mock.ts b/extensions/voice-call/src/providers/mock.ts index 7dcb201ff30..133f9e3d862 100644 --- a/extensions/voice-call/src/providers/mock.ts +++ b/extensions/voice-call/src/providers/mock.ts @@ -1,4 +1,5 @@ import crypto from "node:crypto"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import type { EndReason, GetCallStatusInput, @@ -170,7 +171,7 @@ export class MockProvider implements VoiceCallProvider { } async getCallStatus(input: GetCallStatusInput): Promise { - const id = input.providerCallId.toLowerCase(); + const id = normalizeLowercaseStringOrEmpty(input.providerCallId); if (id.includes("stale") || id.includes("ended") || id.includes("completed")) { return { status: "completed", isTerminal: true }; } diff --git a/extensions/voice-call/src/providers/plivo.ts b/extensions/voice-call/src/providers/plivo.ts index 2e257037e6c..138330ceabd 100644 --- a/extensions/voice-call/src/providers/plivo.ts +++ b/extensions/voice-call/src/providers/plivo.ts @@ -1,4 +1,5 @@ import crypto from "node:crypto"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import type { PlivoConfig, WebhookSecurityConfig } from "../config.js"; import { getHeader } from "../http-headers.js"; import type { @@ -480,7 +481,7 @@ export class PlivoProvider implements VoiceCallProvider { private static normalizeNumber(numberOrSip: string): string { const trimmed = numberOrSip.trim(); - if (trimmed.toLowerCase().startsWith("sip:")) { + if (normalizeLowercaseStringOrEmpty(trimmed).startsWith("sip:")) { return trimmed; } return trimmed.replace(/[^\d+]/g, ""); diff --git a/extensions/voice-call/src/response-generator.ts b/extensions/voice-call/src/response-generator.ts index 4bdae5bf269..85e16891bea 100644 --- a/extensions/voice-call/src/response-generator.ts +++ b/extensions/voice-call/src/response-generator.ts @@ -4,6 +4,7 @@ */ import crypto from "node:crypto"; +import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; import type { SessionEntry } from "../api.js"; import type { VoiceCallConfig } from "./config.js"; import type { CoreAgentDeps, CoreConfig } from "./core-bridge.js"; @@ -95,7 +96,7 @@ function tryParseSpokenJson(text: string): string | null { } function isLikelyMetaReasoningParagraph(paragraph: string): boolean { - const lower = paragraph.toLowerCase(); + const lower = normalizeLowercaseStringOrEmpty(paragraph); if (!lower) { return false; } diff --git a/src/channels/conversation-label.ts b/src/channels/conversation-label.ts index 6f801c6a548..d652dec5a8b 100644 --- a/src/channels/conversation-label.ts +++ b/src/channels/conversation-label.ts @@ -1,5 +1,8 @@ import type { MsgContext } from "../auto-reply/templating.js"; -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalString, +} from "../shared/string-coerce.js"; import { normalizeChatType } from "./chat-type.js"; function extractConversationId(from?: string): string | undefined { @@ -60,7 +63,7 @@ export function resolveConversationLabel(ctx: MsgContext): string | undefined { if (base.includes(id)) { return base; } - if (base.toLowerCase().includes(" id:")) { + if (normalizeLowercaseStringOrEmpty(base).includes(" id:")) { return base; } if (base.startsWith("#") || base.startsWith("@")) { diff --git a/src/channels/plugins/chat-target-prefixes.ts b/src/channels/plugins/chat-target-prefixes.ts index 7cb728f2ea8..fc6bc03bcd2 100644 --- a/src/channels/plugins/chat-target-prefixes.ts +++ b/src/channels/plugins/chat-target-prefixes.ts @@ -1,3 +1,5 @@ +import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; + export type ServicePrefix = { prefix: string; service: TService }; export type ChatTargetPrefixesParams = { @@ -94,7 +96,7 @@ export function resolveServicePrefixedTarget(p if (!remainder) { throw new Error(`${prefix} target is required`); } - const remainderLower = remainder.toLowerCase(); + const remainderLower = normalizeLowercaseStringOrEmpty(remainder); if (params.isChatTarget(remainderLower)) { return params.parseTarget(remainder); }