refactor: remove residual normalization aliases (#122956)

* refactor: remove residual normalization aliases

* test(gateway): isolate approval authority handshake
This commit is contained in:
Peter Steinberger
2026-08-12 20:26:30 -07:00
committed by GitHub
parent 061c9c2f7f
commit ffb2ed9e89
12 changed files with 27 additions and 90 deletions
@@ -1,3 +1,5 @@
import { parseStrictNonNegativeInteger } from "@openclaw/normalization-core/number-coercion";
export interface PromptTemplate {
name: string;
description?: string;
@@ -39,11 +41,6 @@ export function parseCommandArgs(argsString: string): string[] {
return args;
}
function parseSafeNonNegativeInteger(raw: string): number | undefined {
const parsed = Number(raw);
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : undefined;
}
/**
* Substitute prompt template placeholders (`$1`, `$@`, `$ARGUMENTS`, `${@:N}`, `${@:N:L}`) with command arguments.
*
@@ -53,7 +50,7 @@ function parseSafeNonNegativeInteger(raw: string): number | undefined {
export function substituteArgs(content: string, args: string[]): string {
let result = content;
result = result.replace(/\$(\d+)/g, (_, num: string) => {
const parsed = parseSafeNonNegativeInteger(num);
const parsed = parseStrictNonNegativeInteger(num);
if (parsed === undefined || parsed <= 0) {
return "";
}
@@ -62,7 +59,7 @@ export function substituteArgs(content: string, args: string[]): string {
result = result.replace(
/\$\{@:(\d+)(?::(\d+))?\}/g,
(_, startStr: string, lengthStr?: string) => {
const parsedStart = parseSafeNonNegativeInteger(startStr);
const parsedStart = parseStrictNonNegativeInteger(startStr);
if (parsedStart === undefined) {
return "";
}
@@ -73,7 +70,7 @@ export function substituteArgs(content: string, args: string[]): string {
start = 0;
}
if (lengthStr) {
const length = parseSafeNonNegativeInteger(lengthStr);
const length = parseStrictNonNegativeInteger(lengthStr);
if (length === undefined) {
return "";
}
+1 -5
View File
@@ -78,10 +78,6 @@ export function isHelpOrVersionInvocation(argv: string[]): boolean {
return false;
}
function parsePositiveInt(value: string): number | undefined {
return parseStrictPositiveInteger(value);
}
export function hasFlag(argv: string[], name: string): boolean {
const args = argv.slice(2);
for (const arg of args) {
@@ -497,7 +493,7 @@ export function getPositiveIntFlagValue(argv: string[], name: string): number |
}
// Keep absent distinct from present-but-invalid so route-first callers can
// defer invalid input to Commander instead of silently applying defaults.
return parsePositiveInt(raw) ?? null;
return parseStrictPositiveInteger(raw) ?? null;
}
export function getCommandPathWithRootOptions(argv: string[], depth = 2): string[] {
+1 -24
View File
@@ -1,10 +1,6 @@
// Program helper tests cover shared command registration and help helpers.
import { describe, expect, it } from "vitest";
import {
collectOption,
parsePositiveIntOrUndefined,
parseStrictPositiveIntOption,
} from "./helpers.js";
import { collectOption, parseStrictPositiveIntOption } from "./helpers.js";
describe("program helpers", () => {
it("collectOption appends values in order", () => {
@@ -12,25 +8,6 @@ describe("program helpers", () => {
expect(collectOption("b", ["a"])).toEqual(["a", "b"]);
});
it.each([
{ value: undefined, expected: undefined },
{ value: null, expected: undefined },
{ value: "", expected: undefined },
{ value: 5, expected: 5 },
{ value: 5.9, expected: undefined },
{ value: 0, expected: undefined },
{ value: -1, expected: undefined },
{ value: Number.NaN, expected: undefined },
{ value: "10", expected: 10 },
{ value: "10ms", expected: undefined },
{ value: "1.5", expected: undefined },
{ value: "0", expected: undefined },
{ value: "nope", expected: undefined },
{ value: true, expected: undefined },
])("parsePositiveIntOrUndefined(%j)", ({ value, expected }) => {
expect(parsePositiveIntOrUndefined(value)).toBe(expected);
});
it("parseStrictPositiveIntOption rejects partial numeric strings", () => {
expect(parseStrictPositiveIntOption("10", "--limit")).toBe(10);
expect(() => parseStrictPositiveIntOption("10ms", "--limit")).toThrow(
-8
View File
@@ -7,14 +7,6 @@ export function collectOption(value: string, previous: string[] = []): string[]
return [...previous, value];
}
/** Parse an optional positive integer, treating empty values as unset. */
export function parsePositiveIntOrUndefined(value: unknown): number | undefined {
if (value === undefined || value === null || value === "") {
return undefined;
}
return parseStrictPositiveInteger(value);
}
/** Commander argument parser for required positive integer options. */
export function parseStrictPositiveIntOption(value: string, flag: string): number {
const parsed = parseStrictPositiveInteger(value);
@@ -7,7 +7,6 @@ import { setVerbose } from "../../globals.js";
import { defaultRuntime } from "../../runtime.js";
import { runCommandWithRuntime } from "../cli-utils.js";
import { formatHelpExamples } from "../help-format.js";
import { parsePositiveIntOrUndefined } from "./helpers.js";
function resolveVerbose(opts: { verbose?: boolean; debug?: boolean }): boolean {
return Boolean(opts.verbose || opts.debug);
@@ -209,7 +208,7 @@ function registerSessionsLifecycleCommand(
}
function parseTimeoutMs(timeout: unknown): number | null | undefined {
const parsed = parsePositiveIntOrUndefined(timeout);
const parsed = parseStrictPositiveInteger(timeout);
if (timeout !== undefined && parsed === undefined) {
defaultRuntime.error("--timeout must be a positive integer (milliseconds)");
defaultRuntime.exit(1);
+2 -2
View File
@@ -1,4 +1,5 @@
/** CLI entrypoint for channel message actions. */
import { parseStrictPositiveInteger } from "@openclaw/normalization-core/number-coercion";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
@@ -15,7 +16,6 @@ import { formatCliCommand } from "../cli/command-format.js";
import { getScopedChannelsCommandSecretTargets } from "../cli/command-secret-targets.js";
import { resolveMessageSecretScope } from "../cli/message-secret-scope.js";
import { createOutboundSendDeps, type CliDeps } from "../cli/outbound-send-deps.js";
import { parsePositiveIntOrUndefined } from "../cli/program/helpers.js";
import { withProgress } from "../cli/progress.js";
import { getRuntimeConfig } from "../config/config.js";
import type { OutboundSendDeps } from "../infra/outbound/deliver.js";
@@ -159,7 +159,7 @@ export async function messageCommand(
}
const { formatMessageCliText } = await import("./message-format.js");
const displayLimit = parsePositiveIntOrUndefined(opts.limit);
const displayLimit = parseStrictPositiveInteger(opts.limit);
for (const line of formatMessageCliText(result, { displayLimit })) {
runtime.log(line);
}
@@ -12,6 +12,10 @@ import { createChatRunState } from "../server-chat-state.js";
import { createExecApprovalHandlers } from "./exec-approval.js";
import type { GatewayRequestHandlerOptions } from "./types.js";
vi.mock("../../infra/command-analysis/explain.js", () => ({
resolveCommandAnalysisSummaryForDisplay: vi.fn(async () => null),
}));
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
function databaseOptions(): OpenClawStateDatabaseOptions {
@@ -74,10 +74,6 @@ function normalizePlugin(value: unknown): OpenClawProviderIndexPlugin | undefine
};
}
function normalizeCategories(value: unknown): readonly string[] {
return normalizeUniqueTrimmedStringList(value);
}
function normalizePreviewCatalog(params: {
providerId: string;
value: unknown;
@@ -192,7 +188,7 @@ function normalizeProvider(
return undefined;
}
const docs = normalizeOptionalString(value.docs) ?? "";
const categories = normalizeCategories(value.categories);
const categories = normalizeUniqueTrimmedStringList(value.categories);
const authChoices = normalizeAuthChoices({
providerId,
providerName: name,
+3 -7
View File
@@ -228,10 +228,6 @@ function assertWritableInstalledPluginIndexStoreOptions(
}
}
function parseJsonColumn(value: string): unknown {
return safeParseJson(value);
}
function parseInstalledPluginIndexSqliteRow(
row: InstalledPluginIndexSqliteRow | undefined,
): InstalledPluginIndex | null {
@@ -247,9 +243,9 @@ function parseInstalledPluginIndexSqliteRow(
policyHash: row.policy_hash,
generatedAtMs: Number(row.generated_at_ms),
...(row.refresh_reason ? { refreshReason: row.refresh_reason } : {}),
installRecords: parseJsonColumn(row.install_records_json),
plugins: parseJsonColumn(row.plugins_json),
diagnostics: parseJsonColumn(row.diagnostics_json),
installRecords: safeParseJson(row.install_records_json),
plugins: safeParseJson(row.plugins_json),
diagnostics: safeParseJson(row.diagnostics_json),
});
}
-19
View File
@@ -1,19 +0,0 @@
// Persisted settings normalizers shared by the settings storage owner.
/** Unknown shapes fall back to []; stale and duplicate ids are dropped. */
export function normalizePinnedAgentIds(value: unknown): string[] {
if (!Array.isArray(value)) {
return [];
}
const pinned: string[] = [];
for (const entry of value) {
if (typeof entry !== "string") {
continue;
}
const agentId = entry.trim();
if (agentId && !pinned.includes(agentId)) {
pinned.push(agentId);
}
}
return pinned;
}
+2 -2
View File
@@ -1,6 +1,7 @@
import { gatewayOriginScope } from "@openclaw/gateway-client/browser";
import { safeParseJson } from "@openclaw/normalization-core";
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { normalizeUniqueTrimmedStringList } from "@openclaw/normalization-core/string-normalization";
import {
DEFAULT_SIDEBAR_ENTRIES,
normalizeSidebarEntries,
@@ -19,7 +20,6 @@ import {
import { normalizeChatSplitLayout, type ChatSplitLayout } from "../pages/chat/split-layout.ts";
import { resolveControlUiBasePath } from "./browser.ts";
import { parseImportedCustomTheme, type ImportedCustomTheme } from "./custom-theme.ts";
import { normalizePinnedAgentIds } from "./settings-normalizers.ts";
import { parseThemeSelection, type ThemeMode, type ThemeName } from "./theme.ts";
import { normalizeLocalUserIdentity, type LocalUserIdentity } from "./user-identity.ts";
@@ -546,7 +546,7 @@ export function loadSettings(): UiSettings {
typeof parsed.showAdvancedSettings === "boolean"
? parsed.showAdvancedSettings
: defaults.showAdvancedSettings,
pinnedAgentIds: normalizePinnedAgentIds(parsed.pinnedAgentIds),
pinnedAgentIds: normalizeUniqueTrimmedStringList(parsed.pinnedAgentIds),
textScale:
typeof parsed.textScale === "number" &&
normalizeTextScale(parsed.textScale) !== UI_APPEARANCE_DEFAULTS.textScale
+7 -8
View File
@@ -1,4 +1,4 @@
import { sortUniqueStrings } from "@openclaw/normalization-core/string-normalization";
import { normalizeSortedUniqueTrimmedStringList } from "@openclaw/normalization-core/string-normalization";
import type { AgentsListResult } from "../../api/types.ts";
import type { ApplicationContext } from "../../app/context.ts";
import { listSelectableAgents } from "../../lib/agents/display.ts";
@@ -12,10 +12,6 @@ import { resolveCronTimezoneSuggestions } from "./timezone-suggestions.ts";
export const THINKING_SUGGESTIONS = ["off", "minimal", "low", "medium", "high"];
function unique(values: string[]): string[] {
return sortUniqueStrings(values.map((value) => value.trim()).filter(Boolean));
}
export function buildCronSuggestions(params: {
channels: ApplicationContext["channels"]["state"];
runtimeConfig: ApplicationContext["runtimeConfig"]["state"];
@@ -30,7 +26,7 @@ export function buildCronSuggestions(params: {
.filter((entry) => entry.kind === "system")
.map((entry) => entry.id.trim()),
);
const agentSuggestions = unique([
const agentSuggestions = normalizeSortedUniqueTrimmedStringList([
...listSelectableAgents(params.agentsList?.agents ?? []).map((entry) => entry.id.trim()),
...params.cron.cronJobs.map((job) =>
typeof job.agentId === "string" && !systemAgentIds.has(job.agentId.trim())
@@ -38,7 +34,7 @@ export function buildCronSuggestions(params: {
: "",
),
]);
const modelSuggestions = unique([
const modelSuggestions = normalizeSortedUniqueTrimmedStringList([
...params.modelSuggestions,
...resolveConfiguredCronModelSuggestions(configValue),
...params.cron.cronJobs.map((job) => {
@@ -60,7 +56,10 @@ export function buildCronSuggestions(params: {
.filter((value): value is string => typeof value === "string")
.map((value) => value.trim())
.filter(Boolean);
const deliveryTargets = unique([...jobTargets, ...accountTargets]);
const deliveryTargets = normalizeSortedUniqueTrimmedStringList([
...jobTargets,
...accountTargets,
]);
return {
agentSuggestions,
modelSuggestions,