From ffb2ed9e8955903f9cb0df26633d86155fe5d28d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 12 Aug 2026 20:26:30 -0700 Subject: [PATCH] refactor: remove residual normalization aliases (#122956) * refactor: remove residual normalization aliases * test(gateway): isolate approval authority handshake --- .../src/harness/prompt-template-arguments.ts | 13 ++++------ src/cli/argv.ts | 6 +---- src/cli/program/helpers.test.ts | 25 +------------------ src/cli/program/helpers.ts | 8 ------ .../register.status-health-sessions.ts | 3 +-- src/commands/message.ts | 4 +-- .../exec-approval.agent-runtime.test.ts | 4 +++ src/model-catalog/provider-index/normalize.ts | 6 +---- src/plugins/installed-plugin-index-store.ts | 10 +++----- ui/src/app/settings-normalizers.ts | 19 -------------- ui/src/app/settings.ts | 4 +-- ui/src/pages/cron/form-suggestions.ts | 15 ++++++----- 12 files changed, 27 insertions(+), 90 deletions(-) delete mode 100644 ui/src/app/settings-normalizers.ts diff --git a/packages/agent-core/src/harness/prompt-template-arguments.ts b/packages/agent-core/src/harness/prompt-template-arguments.ts index 7d288556a31a..b39e7de3b383 100644 --- a/packages/agent-core/src/harness/prompt-template-arguments.ts +++ b/packages/agent-core/src/harness/prompt-template-arguments.ts @@ -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 ""; } diff --git a/src/cli/argv.ts b/src/cli/argv.ts index 91314724f873..e62635e71c12 100644 --- a/src/cli/argv.ts +++ b/src/cli/argv.ts @@ -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[] { diff --git a/src/cli/program/helpers.test.ts b/src/cli/program/helpers.test.ts index cb49bebd45df..de9aa0dd7c88 100644 --- a/src/cli/program/helpers.test.ts +++ b/src/cli/program/helpers.test.ts @@ -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( diff --git a/src/cli/program/helpers.ts b/src/cli/program/helpers.ts index 7547bc6025b1..59d59c0323c9 100644 --- a/src/cli/program/helpers.ts +++ b/src/cli/program/helpers.ts @@ -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); diff --git a/src/cli/program/register.status-health-sessions.ts b/src/cli/program/register.status-health-sessions.ts index 8b4ced03b83e..554e9316e403 100644 --- a/src/cli/program/register.status-health-sessions.ts +++ b/src/cli/program/register.status-health-sessions.ts @@ -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); diff --git a/src/commands/message.ts b/src/commands/message.ts index e4d8adc33c37..2af22330f4bc 100644 --- a/src/commands/message.ts +++ b/src/commands/message.ts @@ -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); } diff --git a/src/gateway/server-methods/exec-approval.agent-runtime.test.ts b/src/gateway/server-methods/exec-approval.agent-runtime.test.ts index 64c81ff717e9..36f64f6923ac 100644 --- a/src/gateway/server-methods/exec-approval.agent-runtime.test.ts +++ b/src/gateway/server-methods/exec-approval.agent-runtime.test.ts @@ -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 { diff --git a/src/model-catalog/provider-index/normalize.ts b/src/model-catalog/provider-index/normalize.ts index bda0f41ce542..c89da6be0da3 100644 --- a/src/model-catalog/provider-index/normalize.ts +++ b/src/model-catalog/provider-index/normalize.ts @@ -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, diff --git a/src/plugins/installed-plugin-index-store.ts b/src/plugins/installed-plugin-index-store.ts index 0dd0204baec9..4227809d6398 100644 --- a/src/plugins/installed-plugin-index-store.ts +++ b/src/plugins/installed-plugin-index-store.ts @@ -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), }); } diff --git a/ui/src/app/settings-normalizers.ts b/ui/src/app/settings-normalizers.ts deleted file mode 100644 index fb5d65e1bc67..000000000000 --- a/ui/src/app/settings-normalizers.ts +++ /dev/null @@ -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; -} diff --git a/ui/src/app/settings.ts b/ui/src/app/settings.ts index 2ebd30b99e87..a25e54a504f5 100644 --- a/ui/src/app/settings.ts +++ b/ui/src/app/settings.ts @@ -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 diff --git a/ui/src/pages/cron/form-suggestions.ts b/ui/src/pages/cron/form-suggestions.ts index a67ffd5a6972..770f5ac2edfe 100644 --- a/ui/src/pages/cron/form-suggestions.ts +++ b/ui/src/pages/cron/form-suggestions.ts @@ -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,