mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
refactor: dedupe core lowercase helpers
This commit is contained in:
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { parseStrictInteger, parseStrictPositiveInteger } from "../infra/parse-finite-number.js";
|
||||
import { cleanStaleGatewayProcessesSync } from "../infra/restart-stale-pids.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import {
|
||||
GATEWAY_LAUNCH_AGENT_LABEL,
|
||||
resolveGatewayServiceDescription,
|
||||
@@ -301,7 +302,7 @@ export async function readLaunchAgentRuntime(
|
||||
}
|
||||
const parsed = parseLaunchctlPrint(res.stdout || res.stderr || "");
|
||||
const plistExists = await launchAgentPlistExists(env);
|
||||
const state = parsed.state?.toLowerCase();
|
||||
const state = normalizeLowercaseStringOrEmpty(parsed.state);
|
||||
const status = state === "running" || parsed.pid ? "running" : state ? "stopped" : "unknown";
|
||||
return {
|
||||
status,
|
||||
@@ -331,7 +332,7 @@ export async function repairLaunchAgentBootstrap(args: {
|
||||
let repairStatus: LaunchAgentBootstrapRepairResult["status"] = "repaired";
|
||||
if (boot.code !== 0) {
|
||||
const detail = (boot.stderr || boot.stdout).trim();
|
||||
const normalized = detail.toLowerCase();
|
||||
const normalized = normalizeLowercaseStringOrEmpty(detail);
|
||||
const alreadyLoaded = boot.code === 130 || normalized.includes("already exists in domain");
|
||||
if (!alreadyLoaded) {
|
||||
return { ok: false, status: "bootstrap-failed", detail: detail || undefined };
|
||||
@@ -447,7 +448,7 @@ export async function uninstallLaunchAgent({
|
||||
}
|
||||
|
||||
function isLaunchctlNotLoaded(res: { stdout: string; stderr: string; code: number }): boolean {
|
||||
const detail = (res.stderr || res.stdout).toLowerCase();
|
||||
const detail = normalizeLowercaseStringOrEmpty(res.stderr || res.stdout);
|
||||
return (
|
||||
detail.includes("no such process") ||
|
||||
detail.includes("could not find service") ||
|
||||
@@ -456,7 +457,7 @@ function isLaunchctlNotLoaded(res: { stdout: string; stderr: string; code: numbe
|
||||
}
|
||||
|
||||
function isUnsupportedGuiDomain(detail: string): boolean {
|
||||
const normalized = detail.toLowerCase();
|
||||
const normalized = normalizeLowercaseStringOrEmpty(detail);
|
||||
return (
|
||||
normalized.includes("domain does not support specified action") ||
|
||||
normalized.includes("bootstrap failed: 125")
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
|
||||
const NODE_VERSIONED_PATTERN = /^node(?:-\d+|\d+)(?:\.\d+)*(?:\.exe)?$/;
|
||||
|
||||
function normalizeRuntimeBasename(execPath: string): string {
|
||||
const trimmed = execPath.trim().replace(/^["']|["']$/g, "");
|
||||
const lastSlash = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
||||
const basename = lastSlash === -1 ? trimmed : trimmed.slice(lastSlash + 1);
|
||||
return basename.toLowerCase();
|
||||
return normalizeLowercaseStringOrEmpty(basename);
|
||||
}
|
||||
|
||||
export function isNodeRuntime(execPath: string): boolean {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { promisify } from "node:util";
|
||||
import { isSupportedNodeVersion } from "../infra/runtime-guard.js";
|
||||
import { resolveStableNodePath } from "../infra/stable-node-path.js";
|
||||
import { getWindowsProgramFilesRoots } from "../infra/windows-install-roots.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
|
||||
const VERSION_MANAGER_MARKERS = [
|
||||
"/.nvm/",
|
||||
@@ -23,7 +24,7 @@ function getPathModule(platform: NodeJS.Platform) {
|
||||
|
||||
function isNodeExecPath(execPath: string, platform: NodeJS.Platform): boolean {
|
||||
const pathModule = getPathModule(platform);
|
||||
const base = pathModule.basename(execPath).toLowerCase();
|
||||
const base = normalizeLowercaseStringOrEmpty(pathModule.basename(execPath));
|
||||
return base === "node" || base === "node.exe";
|
||||
}
|
||||
|
||||
@@ -31,7 +32,7 @@ function normalizeForCompare(input: string, platform: NodeJS.Platform): string {
|
||||
const pathModule = getPathModule(platform);
|
||||
const normalized = pathModule.normalize(input).replaceAll("\\", "/");
|
||||
if (platform === "win32") {
|
||||
return normalized.toLowerCase();
|
||||
return normalizeLowercaseStringOrEmpty(normalized);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { resolveLaunchAgentPlistPath } from "./launchd.js";
|
||||
import { isBunRuntime, isNodeRuntime } from "./runtime-binary.js";
|
||||
import {
|
||||
@@ -252,7 +255,7 @@ function normalizePathEntry(entry: string, platform: NodeJS.Platform): string {
|
||||
const pathModule = getPathModule(platform);
|
||||
const normalized = pathModule.normalize(entry).replaceAll("\\", "/");
|
||||
if (platform === "win32") {
|
||||
return normalized.toLowerCase();
|
||||
return normalizeLowercaseStringOrEmpty(normalized);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { parseStrictInteger, parseStrictPositiveInteger } from "../infra/parse-finite-number.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { splitArgsPreservingQuotes } from "./arg-split.js";
|
||||
import {
|
||||
LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES,
|
||||
@@ -277,7 +278,7 @@ function isSystemdUnitNotEnabled(detail: string): boolean {
|
||||
if (!detail) {
|
||||
return false;
|
||||
}
|
||||
const normalized = detail.toLowerCase();
|
||||
const normalized = normalizeLowercaseStringOrEmpty(detail);
|
||||
return (
|
||||
normalized.includes("disabled") ||
|
||||
normalized.includes("static") ||
|
||||
@@ -317,7 +318,7 @@ export function isNonFatalSystemdInstallProbeError(error: unknown): boolean {
|
||||
if (!detail) {
|
||||
return false;
|
||||
}
|
||||
const normalized = detail.toLowerCase();
|
||||
const normalized = normalizeLowercaseStringOrEmpty(detail);
|
||||
return isSystemctlBusUnavailable(normalized) || isGenericSystemctlIsEnabledFailure(normalized);
|
||||
}
|
||||
|
||||
@@ -628,7 +629,7 @@ export async function readSystemdServiceRuntime(
|
||||
]);
|
||||
if (res.code !== 0) {
|
||||
const detail = (res.stderr || res.stdout).trim();
|
||||
const missing = detail.toLowerCase().includes("not found");
|
||||
const missing = normalizeLowercaseStringOrEmpty(detail).includes("not found");
|
||||
return {
|
||||
status: missing ? "stopped" : "unknown",
|
||||
detail: detail || undefined,
|
||||
@@ -636,7 +637,7 @@ export async function readSystemdServiceRuntime(
|
||||
};
|
||||
}
|
||||
const parsed = parseSystemdShow(res.stdout || "");
|
||||
const activeState = parsed.activeState?.toLowerCase();
|
||||
const activeState = normalizeLowercaseStringOrEmpty(parsed.activeState);
|
||||
const status = activeState === "active" ? "running" : activeState ? "stopped" : "unknown";
|
||||
return {
|
||||
status,
|
||||
@@ -713,4 +714,3 @@ export async function uninstallLegacySystemdUnits({
|
||||
|
||||
return units;
|
||||
}
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Chalk } from "chalk";
|
||||
import type { Logger as TsLogger } from "tslog";
|
||||
import { isVerbose } from "../global-state.js";
|
||||
import { defaultRuntime, type OutputRuntimeEnv, type RuntimeEnv } from "../runtime.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { clearActiveProgressLine } from "../terminal/progress-line.js";
|
||||
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
||||
import {
|
||||
@@ -73,7 +74,7 @@ function formatRuntimeArg(arg: unknown): string {
|
||||
}
|
||||
|
||||
function isRichConsoleEnv(): boolean {
|
||||
const term = (process.env.TERM ?? "").toLowerCase();
|
||||
const term = normalizeLowercaseStringOrEmpty(process.env.TERM);
|
||||
if (process.env.COLORTERM || process.env.TERM_PROGRAM) {
|
||||
return true;
|
||||
}
|
||||
@@ -151,7 +152,10 @@ export function stripRedundantSubsystemPrefixForConsole(
|
||||
const closeIdx = message.indexOf("]");
|
||||
if (closeIdx > 1) {
|
||||
const bracketTag = message.slice(1, closeIdx);
|
||||
if (bracketTag.toLowerCase() === displaySubsystem.toLowerCase()) {
|
||||
if (
|
||||
normalizeLowercaseStringOrEmpty(bracketTag) ===
|
||||
normalizeLowercaseStringOrEmpty(displaySubsystem)
|
||||
) {
|
||||
let i = closeIdx + 1;
|
||||
while (message[i] === " ") {
|
||||
i += 1;
|
||||
@@ -162,7 +166,9 @@ export function stripRedundantSubsystemPrefixForConsole(
|
||||
}
|
||||
|
||||
const prefix = message.slice(0, displaySubsystem.length);
|
||||
if (prefix.toLowerCase() !== displaySubsystem.toLowerCase()) {
|
||||
if (
|
||||
normalizeLowercaseStringOrEmpty(prefix) !== normalizeLowercaseStringOrEmpty(displaySubsystem)
|
||||
) {
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ import { GatewayClient } from "../gateway/client.js";
|
||||
import { APPROVALS_SCOPE, READ_SCOPE, WRITE_SCOPE } from "../gateway/method-scopes.js";
|
||||
import type { EventFrame } from "../gateway/protocol/index.js";
|
||||
import { extractFirstTextBlock } from "../shared/chat-message-content.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import type {
|
||||
@@ -161,12 +165,14 @@ export class OpenClawChannelBridge {
|
||||
includeDerivedTitles: params?.includeDerivedTitles ?? true,
|
||||
includeLastMessage: params?.includeLastMessage ?? true,
|
||||
});
|
||||
const requestedChannel = toText(params?.channel)?.toLowerCase();
|
||||
const requestedChannel = normalizeOptionalLowercaseString(params?.channel);
|
||||
return (response.sessions ?? [])
|
||||
.map(toConversation)
|
||||
.filter((conversation): conversation is ConversationDescriptor => Boolean(conversation))
|
||||
.filter((conversation) =>
|
||||
requestedChannel ? conversation.channel.toLowerCase() === requestedChannel : true,
|
||||
requestedChannel
|
||||
? normalizeLowercaseStringOrEmpty(conversation.channel) === requestedChannel
|
||||
: true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -448,14 +454,16 @@ export class OpenClawChannelBridge {
|
||||
const text = extractFirstTextBlock(payload.message);
|
||||
const permissionMatch = text ? CLAUDE_PERMISSION_REPLY_RE.exec(text) : null;
|
||||
if (permissionMatch) {
|
||||
const requestId = permissionMatch[2]?.toLowerCase();
|
||||
const requestId = normalizeOptionalLowercaseString(permissionMatch[2]);
|
||||
if (requestId && this.pendingClaudePermissions.has(requestId)) {
|
||||
this.pendingClaudePermissions.delete(requestId);
|
||||
await this.sendNotification({
|
||||
method: "notifications/claude/channel/permission",
|
||||
params: {
|
||||
request_id: requestId,
|
||||
behavior: permissionMatch[1]?.toLowerCase().startsWith("y") ? "allow" : "deny",
|
||||
behavior: normalizeLowercaseStringOrEmpty(permissionMatch[1]).startsWith("y")
|
||||
? "allow"
|
||||
: "deny",
|
||||
},
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -231,7 +231,7 @@ function normalizeStringArray<T extends string>(
|
||||
const allowedSet = new Set(allowed);
|
||||
const normalized: T[] = [];
|
||||
for (const entry of value) {
|
||||
const normalizedEntry = normalizeTrimmedString(entry)?.toLowerCase();
|
||||
const normalizedEntry = normalizeOptionalLowercaseString(entry);
|
||||
if (!normalizedEntry || !allowedSet.has(normalizedEntry as T)) {
|
||||
continue;
|
||||
}
|
||||
@@ -243,7 +243,7 @@ function normalizeStringArray<T extends string>(
|
||||
}
|
||||
|
||||
function normalizeStorageMode(value: unknown): MemoryDreamingStorageMode {
|
||||
const normalized = normalizeTrimmedString(value)?.toLowerCase();
|
||||
const normalized = normalizeOptionalLowercaseString(value);
|
||||
if (normalized === "inline" || normalized === "separate" || normalized === "both") {
|
||||
return normalized;
|
||||
}
|
||||
@@ -251,7 +251,7 @@ function normalizeStorageMode(value: unknown): MemoryDreamingStorageMode {
|
||||
}
|
||||
|
||||
function normalizeSpeed(value: unknown): MemoryDreamingSpeed | undefined {
|
||||
const normalized = normalizeTrimmedString(value)?.toLowerCase();
|
||||
const normalized = normalizeOptionalLowercaseString(value);
|
||||
if (normalized === "fast" || normalized === "balanced" || normalized === "slow") {
|
||||
return normalized;
|
||||
}
|
||||
@@ -259,7 +259,7 @@ function normalizeSpeed(value: unknown): MemoryDreamingSpeed | undefined {
|
||||
}
|
||||
|
||||
function normalizeThinking(value: unknown): MemoryDreamingThinking | undefined {
|
||||
const normalized = normalizeTrimmedString(value)?.toLowerCase();
|
||||
const normalized = normalizeOptionalLowercaseString(value);
|
||||
if (normalized === "low" || normalized === "medium" || normalized === "high") {
|
||||
return normalized;
|
||||
}
|
||||
@@ -267,7 +267,7 @@ function normalizeThinking(value: unknown): MemoryDreamingThinking | undefined {
|
||||
}
|
||||
|
||||
function normalizeBudget(value: unknown): MemoryDreamingBudget | undefined {
|
||||
const normalized = normalizeTrimmedString(value)?.toLowerCase();
|
||||
const normalized = normalizeOptionalLowercaseString(value);
|
||||
if (normalized === "cheap" || normalized === "medium" || normalized === "expensive") {
|
||||
return normalized;
|
||||
}
|
||||
@@ -320,7 +320,7 @@ export function resolveMemoryDreamingPluginId(
|
||||
const plugins = asNullableRecord(root?.plugins);
|
||||
const slots = asNullableRecord(plugins?.slots);
|
||||
const configuredSlot = normalizeTrimmedString(slots?.memory);
|
||||
if (configuredSlot && configuredSlot.toLowerCase() !== "none") {
|
||||
if (configuredSlot && normalizeLowercaseStringOrEmpty(configuredSlot) !== "none") {
|
||||
return configuredSlot;
|
||||
}
|
||||
return DEFAULT_MEMORY_DREAMING_PLUGIN_ID;
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
MemoryQmdSearchMode,
|
||||
} from "../../config/types.memory.js";
|
||||
import { normalizeAgentId } from "../../routing/session-key.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import { resolveUserPath } from "../../utils.js";
|
||||
import { splitShellArgs } from "../../utils/shell-argv.js";
|
||||
|
||||
@@ -107,7 +108,7 @@ const DEFAULT_QMD_SCOPE: SessionSendPolicyConfig = {
|
||||
};
|
||||
|
||||
function sanitizeName(input: string): string {
|
||||
const lower = input.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
|
||||
const lower = normalizeLowercaseStringOrEmpty(input).replace(/[^a-z0-9-]+/g, "-");
|
||||
const trimmed = lower.replace(/^-+|-+$/g, "");
|
||||
return trimmed || "collection";
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import type { EmbeddingProvider } from "./embeddings.js";
|
||||
|
||||
const DEFAULT_EMBEDDING_MAX_INPUT_TOKENS = 8192;
|
||||
@@ -22,7 +23,7 @@ export function resolveEmbeddingMaxInputTokens(provider: EmbeddingProvider): num
|
||||
|
||||
// Provider/model mapping is best-effort; different providers use different
|
||||
// limits and we prefer to be conservative when we don't know.
|
||||
const key = `${provider.id}:${provider.model}`.toLowerCase();
|
||||
const key = normalizeLowercaseStringOrEmpty(`${provider.id}:${provider.model}`);
|
||||
const known = KNOWN_EMBEDDING_MAX_INPUT_TOKENS[key];
|
||||
if (typeof known === "number") {
|
||||
return known;
|
||||
@@ -30,10 +31,10 @@ export function resolveEmbeddingMaxInputTokens(provider: EmbeddingProvider): num
|
||||
|
||||
// Provider-specific conservative fallbacks. This prevents us from accidentally
|
||||
// using the OpenAI default for providers with much smaller limits.
|
||||
if (provider.id.toLowerCase() === "gemini") {
|
||||
if (normalizeLowercaseStringOrEmpty(provider.id) === "gemini") {
|
||||
return 2048;
|
||||
}
|
||||
if (provider.id.toLowerCase() === "local") {
|
||||
if (normalizeLowercaseStringOrEmpty(provider.id) === "local") {
|
||||
return DEFAULT_LOCAL_EMBEDDING_MAX_INPUT_TOKENS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import { sanitizeAndNormalizeEmbedding } from "./embedding-vectors.js";
|
||||
import { debugEmbeddingsLog } from "./embeddings-debug.js";
|
||||
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.js";
|
||||
@@ -73,7 +74,7 @@ function resolveSpec(modelId: string): ModelSpec | undefined {
|
||||
|
||||
/** Infer family from model ID prefix when not in catalog. */
|
||||
function inferFamily(modelId: string): Family {
|
||||
const id = modelId.toLowerCase();
|
||||
const id = normalizeLowercaseStringOrEmpty(modelId);
|
||||
if (id.startsWith("amazon.titan-embed-text-v2")) {
|
||||
return "titan-v2";
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ function parseQmdSessionScope(key?: string): ParsedQmdSessionScope {
|
||||
}
|
||||
return {
|
||||
normalizedKey: normalized,
|
||||
channel: parts[0]?.toLowerCase(),
|
||||
channel: normalizeOptionalLowercaseString(parts[0]),
|
||||
chatType: chatType ?? "direct",
|
||||
};
|
||||
}
|
||||
@@ -102,7 +102,7 @@ function normalizeQmdSessionKey(key?: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = parseAgentSessionKey(trimmed);
|
||||
const normalized = (parsed?.rest ?? trimmed).toLowerCase();
|
||||
const normalized = normalizeLowercaseStringOrEmpty(parsed?.rest ?? trimmed);
|
||||
if (normalized.startsWith("subagent:")) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ import {
|
||||
resolveInlineCommandMatch,
|
||||
} from "../infra/shell-inline-command.js";
|
||||
import { formatExecCommand, resolveSystemRunCommandRequest } from "../infra/system-run-command.js";
|
||||
import { normalizeNullableString } from "../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeNullableString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { splitShellArgs } from "../utils/shell-argv.js";
|
||||
|
||||
export type ApprovedCwdSnapshot = {
|
||||
@@ -138,6 +141,10 @@ const NODE_OPTIONS_WITH_FILE_VALUE = new Set([
|
||||
const RUBY_UNSAFE_APPROVAL_FLAGS = new Set(["-I", "-r", "--require"]);
|
||||
const PERL_UNSAFE_APPROVAL_FLAGS = new Set(["-I", "-M", "-m"]);
|
||||
|
||||
function normalizeOptionFlag(token: string): string {
|
||||
return normalizeLowercaseStringOrEmpty(token.split("=", 1)[0]);
|
||||
}
|
||||
|
||||
const POSIX_SHELL_OPTIONS_WITH_VALUE = new Set([
|
||||
"--init-file",
|
||||
"--rcfile",
|
||||
@@ -353,7 +360,7 @@ function unwrapPnpmExecInvocation(argv: string[]): string[] | null {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const [flag] = token.toLowerCase().split("=", 2);
|
||||
const flag = normalizeOptionFlag(token);
|
||||
if (PNPM_OPTIONS_WITH_VALUE.has(flag) || PNPM_DLX_OPTIONS_WITH_VALUE.has(flag)) {
|
||||
idx += token.includes("=") ? 1 : 2;
|
||||
continue;
|
||||
@@ -384,7 +391,7 @@ function unwrapPnpmDlxInvocation(argv: string[]): string[] | null {
|
||||
// package binary pnpm will execute inside the temporary environment.
|
||||
return argv.slice(idx);
|
||||
}
|
||||
const [flag] = token.toLowerCase().split("=", 2);
|
||||
const flag = normalizeOptionFlag(token);
|
||||
if (flag === "-c" || flag === "--shell-mode") {
|
||||
return null;
|
||||
}
|
||||
@@ -412,7 +419,7 @@ function unwrapDirectPackageExecInvocation(argv: string[]): string[] | null {
|
||||
if (!token.startsWith("-")) {
|
||||
return argv.slice(idx);
|
||||
}
|
||||
const [flag] = token.toLowerCase().split("=", 2);
|
||||
const flag = normalizeOptionFlag(token);
|
||||
if (flag === "-c" || flag === "--call") {
|
||||
return null;
|
||||
}
|
||||
@@ -488,7 +495,7 @@ function resolvePosixShellScriptOperandIndex(argv: string[]): number | null {
|
||||
return null;
|
||||
}
|
||||
if (!afterDoubleDash && token.startsWith("-")) {
|
||||
const [flag] = token.toLowerCase().split("=", 2);
|
||||
const flag = normalizeOptionFlag(token);
|
||||
if (POSIX_SHELL_OPTIONS_WITH_VALUE.has(flag)) {
|
||||
if (!token.includes("=")) {
|
||||
i += 1;
|
||||
@@ -595,7 +602,7 @@ function collectExistingFileOperandIndexes(params: {
|
||||
}
|
||||
if (token.startsWith("-")) {
|
||||
const [flag, inlineValue] = token.split("=", 2);
|
||||
if (params.optionsWithFileValue?.has(flag.toLowerCase())) {
|
||||
if (params.optionsWithFileValue?.has(normalizeLowercaseStringOrEmpty(flag))) {
|
||||
if (inlineValue && resolvesToExistingFileSync(inlineValue, params.cwd)) {
|
||||
hits.push(i);
|
||||
return { hits, sawOptionValueFile: true };
|
||||
@@ -697,7 +704,7 @@ function hasRubyUnsafeApprovalFlag(argv: string[]): boolean {
|
||||
if (token.startsWith("-I") || token.startsWith("-r")) {
|
||||
return true;
|
||||
}
|
||||
if (RUBY_UNSAFE_APPROVAL_FLAGS.has(token.toLowerCase())) {
|
||||
if (RUBY_UNSAFE_APPROVAL_FLAGS.has(normalizeLowercaseStringOrEmpty(token))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -851,7 +858,7 @@ function pnpmDlxInvocationNeedsFailClosedBinding(argv: string[], cwd: string | u
|
||||
}
|
||||
return pnpmDlxTailNeedsFailClosedBinding(argv.slice(idx + 1), cwd);
|
||||
}
|
||||
const [flag] = token.toLowerCase().split("=", 2);
|
||||
const flag = normalizeOptionFlag(token);
|
||||
if (PNPM_OPTIONS_WITH_VALUE.has(flag) || PNPM_DLX_OPTIONS_WITH_VALUE.has(flag)) {
|
||||
idx += token.includes("=") ? 1 : 2;
|
||||
continue;
|
||||
@@ -880,7 +887,7 @@ function pnpmDlxTailNeedsFailClosedBinding(argv: string[], cwd: string | undefin
|
||||
if (!token.startsWith("-")) {
|
||||
return pnpmDlxTailMayNeedStableBinding(argv.slice(idx), cwd);
|
||||
}
|
||||
const [flag] = token.toLowerCase().split("=", 2);
|
||||
const flag = normalizeOptionFlag(token);
|
||||
if (flag === "-c" || flag === "--shell-mode") {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { SlashCommand } from "@mariozechner/pi-tui";
|
||||
import { listChatCommands, listChatCommandsForConfig } from "../auto-reply/commands-registry.js";
|
||||
import { formatThinkingLevels, listThinkingLevelLabels } from "../auto-reply/thinking.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
|
||||
const VERBOSE_LEVELS = ["on", "off"];
|
||||
const FAST_LEVELS = ["status", "on", "off"];
|
||||
@@ -31,7 +32,7 @@ function createLevelCompletion(
|
||||
): NonNullable<SlashCommand["getArgumentCompletions"]> {
|
||||
return (prefix) =>
|
||||
levels
|
||||
.filter((value) => value.startsWith(prefix.toLowerCase()))
|
||||
.filter((value) => value.startsWith(normalizeLowercaseStringOrEmpty(prefix)))
|
||||
.map((value) => ({
|
||||
value,
|
||||
label: value,
|
||||
@@ -44,7 +45,7 @@ export function parseCommand(input: string): ParsedCommand {
|
||||
return { name: "", args: "" };
|
||||
}
|
||||
const [name, ...rest] = trimmed.split(/\s+/);
|
||||
const normalized = name.toLowerCase();
|
||||
const normalized = normalizeLowercaseStringOrEmpty(name);
|
||||
return {
|
||||
name: COMMAND_ALIASES[normalized] ?? normalized,
|
||||
args: rest.join(" ").trim(),
|
||||
@@ -77,7 +78,7 @@ export function getSlashCommands(options: SlashCommandOptions = {}): SlashComman
|
||||
description: "Set thinking level",
|
||||
getArgumentCompletions: (prefix) =>
|
||||
thinkLevels
|
||||
.filter((v) => v.startsWith(prefix.toLowerCase()))
|
||||
.filter((v) => v.startsWith(normalizeLowercaseStringOrEmpty(prefix)))
|
||||
.map((value) => ({ value, label: value })),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type SelectListTheme,
|
||||
} from "@mariozechner/pi-tui";
|
||||
import chalk from "chalk";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import { fuzzyFilterLower, prepareSearchItems } from "./fuzzy-filter.js";
|
||||
|
||||
export interface FilterableSelectItem extends SelectItem {
|
||||
@@ -44,7 +45,7 @@ export class FilterableSelectList implements Component {
|
||||
}
|
||||
|
||||
private applyFilter(): void {
|
||||
const queryLower = this.filterText.toLowerCase();
|
||||
const queryLower = normalizeLowercaseStringOrEmpty(this.filterText);
|
||||
if (!queryLower.trim()) {
|
||||
this.selectList = new SelectList(this.allItems, this.maxVisible, this.theme);
|
||||
return;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Shared fuzzy filtering utilities for select list components.
|
||||
*/
|
||||
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
|
||||
/**
|
||||
* Word boundary characters for matching.
|
||||
*/
|
||||
@@ -22,8 +24,8 @@ export function findWordBoundaryIndex(text: string, query: string): number | nul
|
||||
if (!query) {
|
||||
return null;
|
||||
}
|
||||
const textLower = text.toLowerCase();
|
||||
const queryLower = query.toLowerCase();
|
||||
const textLower = normalizeLowercaseStringOrEmpty(text);
|
||||
const queryLower = normalizeLowercaseStringOrEmpty(query);
|
||||
const maxIndex = textLower.length - queryLower.length;
|
||||
if (maxIndex < 0) {
|
||||
return null;
|
||||
@@ -133,6 +135,6 @@ export function prepareSearchItems<
|
||||
if (item.searchText) {
|
||||
parts.push(item.searchText);
|
||||
}
|
||||
return { ...item, searchTextLower: parts.join(" ").toLowerCase() };
|
||||
return { ...item, searchTextLower: normalizeLowercaseStringOrEmpty(parts.join(" ")) };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type SelectListTheme,
|
||||
truncateToWidth,
|
||||
} from "@mariozechner/pi-tui";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import { stripAnsi, visibleWidth } from "../../terminal/ansi.js";
|
||||
import { findWordBoundaryIndex, fuzzyFilterLower } from "./fuzzy-filter.js";
|
||||
|
||||
@@ -80,7 +81,7 @@ export class SearchableSelectList implements Component {
|
||||
* 4. Fuzzy match (lowest priority)
|
||||
*/
|
||||
private smartFilter(query: string): SelectItem[] {
|
||||
const q = query.toLowerCase();
|
||||
const q = normalizeLowercaseStringOrEmpty(query);
|
||||
type ScoredItem = { item: SelectItem; tier: number; score: number };
|
||||
type FuzzyCandidate = { item: SelectItem; searchTextLower: string };
|
||||
const scoredItems: ScoredItem[] = [];
|
||||
@@ -89,8 +90,8 @@ export class SearchableSelectList implements Component {
|
||||
for (const item of this.items) {
|
||||
const rawLabel = this.getItemLabel(item);
|
||||
const rawDesc = item.description ?? "";
|
||||
const label = stripAnsi(rawLabel).toLowerCase();
|
||||
const desc = stripAnsi(rawDesc).toLowerCase();
|
||||
const label = normalizeLowercaseStringOrEmpty(stripAnsi(rawLabel));
|
||||
const desc = normalizeLowercaseStringOrEmpty(stripAnsi(rawDesc));
|
||||
|
||||
// Tier 1: Exact substring in label
|
||||
const labelIndex = label.indexOf(q);
|
||||
@@ -114,11 +115,12 @@ export class SearchableSelectList implements Component {
|
||||
const searchText = (item as { searchText?: string }).searchText ?? "";
|
||||
fuzzyCandidates.push({
|
||||
item,
|
||||
searchTextLower: [rawLabel, rawDesc, searchText]
|
||||
.map((value) => stripAnsi(value))
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.toLowerCase(),
|
||||
searchTextLower: normalizeLowercaseStringOrEmpty(
|
||||
[rawLabel, rawDesc, searchText]
|
||||
.map((value) => stripAnsi(value))
|
||||
.filter(Boolean)
|
||||
.join(" "),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -171,7 +173,7 @@ export class SearchableSelectList implements Component {
|
||||
const tokens = query
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.map((token) => token.toLowerCase())
|
||||
.map((token) => normalizeLowercaseStringOrEmpty(token))
|
||||
.filter((token) => token.length > 0);
|
||||
if (tokens.length === 0) {
|
||||
return text;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
|
||||
export function createEditorSubmitHandler(params: {
|
||||
editor: {
|
||||
setText: (value: string) => void;
|
||||
@@ -44,7 +46,7 @@ export function shouldEnableWindowsGitBashPasteFallback(params?: {
|
||||
}): boolean {
|
||||
const platform = params?.platform ?? process.platform;
|
||||
const env = params?.env ?? process.env;
|
||||
const termProgram = (env.TERM_PROGRAM ?? "").toLowerCase();
|
||||
const termProgram = normalizeLowercaseStringOrEmpty(env.TERM_PROGRAM);
|
||||
|
||||
// Some macOS terminals emit multiline paste as rapid single-line submits.
|
||||
// Enable burst coalescing so pasted blocks stay as one user message.
|
||||
@@ -64,7 +66,7 @@ export function shouldEnableWindowsGitBashPasteFallback(params?: {
|
||||
if (msystem.startsWith("MINGW") || msystem.startsWith("MSYS")) {
|
||||
return true;
|
||||
}
|
||||
if (shell.toLowerCase().includes("bash")) {
|
||||
if (normalizeLowercaseStringOrEmpty(shell).includes("bash")) {
|
||||
return true;
|
||||
}
|
||||
return termProgram.includes("mintty");
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
normalizeMainKey,
|
||||
parseAgentSessionKey,
|
||||
} from "../routing/session-key.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { getSlashCommands } from "./commands.js";
|
||||
import { ChatLog } from "./components/chat-log.js";
|
||||
import { CustomEditor } from "./components/custom-editor.js";
|
||||
@@ -69,9 +70,9 @@ export function resolveTuiSessionKey(params: {
|
||||
return trimmed;
|
||||
}
|
||||
if (trimmed.startsWith("agent:")) {
|
||||
return trimmed.toLowerCase();
|
||||
return normalizeLowercaseStringOrEmpty(trimmed);
|
||||
}
|
||||
return `agent:${params.currentAgentId}:${trimmed.toLowerCase()}`;
|
||||
return `agent:${params.currentAgentId}:${normalizeLowercaseStringOrEmpty(trimmed)}`;
|
||||
}
|
||||
|
||||
export function resolveInitialTuiAgentId(params: {
|
||||
|
||||
Reference in New Issue
Block a user