From c46f395bb91eeb237ae44f0351c3b46d45ac90c8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 15 Feb 2026 06:02:50 +0000 Subject: [PATCH] refactor(gateway): dedupe config raw validation --- src/gateway/server-methods/config.ts | 141 +++++++++++++-------------- 1 file changed, 68 insertions(+), 73 deletions(-) diff --git a/src/gateway/server-methods/config.ts b/src/gateway/server-methods/config.ts index 807451842..9fb740e99 100644 --- a/src/gateway/server-methods/config.ts +++ b/src/gateway/server-methods/config.ts @@ -1,3 +1,4 @@ +import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { GatewayRequestHandlers, RespondFn } from "./types.js"; import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js"; import { listChannelPlugins } from "../../channels/plugins/index.js"; @@ -93,6 +94,65 @@ function requireConfigBaseHash( return true; } +function parseRawConfigOrRespond( + params: unknown, + requestName: string, + respond: RespondFn, +): string | null { + const rawValue = (params as { raw?: unknown }).raw; + if (typeof rawValue !== "string") { + respond( + false, + undefined, + errorShape( + ErrorCodes.INVALID_REQUEST, + `invalid ${requestName} params: raw (string) required`, + ), + ); + return null; + } + return rawValue; +} + +function parseValidateConfigFromRawOrRespond( + params: unknown, + requestName: string, + snapshot: Awaited>, + respond: RespondFn, +): { config: OpenClawConfig; schema: ConfigSchemaResponse } | null { + const rawValue = parseRawConfigOrRespond(params, requestName, respond); + if (!rawValue) { + return null; + } + const parsedRes = parseConfigJson5(rawValue); + if (!parsedRes.ok) { + respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, parsedRes.error)); + return null; + } + const schema = loadSchemaWithPlugins(); + const restored = restoreRedactedValues(parsedRes.parsed, snapshot.config, schema.uiHints); + if (!restored.ok) { + respond( + false, + undefined, + errorShape(ErrorCodes.INVALID_REQUEST, restored.humanReadableMessage ?? "invalid config"), + ); + return null; + } + const validated = validateConfigObjectWithPlugins(restored.result); + if (!validated.ok) { + respond( + false, + undefined, + errorShape(ErrorCodes.INVALID_REQUEST, "invalid config", { + details: { issues: validated.issues }, + }), + ); + return null; + } + return { config: validated.config, schema }; +} + function resolveConfigRestartRequest(params: unknown): { sessionKey: string | undefined; note: string | undefined; @@ -243,48 +303,17 @@ export const configHandlers: GatewayRequestHandlers = { if (!requireConfigBaseHash(params, snapshot, respond)) { return; } - const rawValue = (params as { raw?: unknown }).raw; - if (typeof rawValue !== "string") { - respond( - false, - undefined, - errorShape(ErrorCodes.INVALID_REQUEST, "invalid config.set params: raw (string) required"), - ); + const parsed = parseValidateConfigFromRawOrRespond(params, "config.set", snapshot, respond); + if (!parsed) { return; } - const parsedRes = parseConfigJson5(rawValue); - if (!parsedRes.ok) { - respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, parsedRes.error)); - return; - } - const schemaSet = loadSchemaWithPlugins(); - const restored = restoreRedactedValues(parsedRes.parsed, snapshot.config, schemaSet.uiHints); - if (!restored.ok) { - respond( - false, - undefined, - errorShape(ErrorCodes.INVALID_REQUEST, restored.humanReadableMessage ?? "invalid config"), - ); - return; - } - const validated = validateConfigObjectWithPlugins(restored.result); - if (!validated.ok) { - respond( - false, - undefined, - errorShape(ErrorCodes.INVALID_REQUEST, "invalid config", { - details: { issues: validated.issues }, - }), - ); - return; - } - await writeConfigFile(validated.config, writeOptions); + await writeConfigFile(parsed.config, writeOptions); respond( true, { ok: true, path: CONFIG_PATH, - config: redactConfigObject(validated.config, schemaSet.uiHints), + config: redactConfigObject(parsed.config, parsed.schema.uiHints), }, undefined, ); @@ -419,45 +448,11 @@ export const configHandlers: GatewayRequestHandlers = { if (!requireConfigBaseHash(params, snapshot, respond)) { return; } - const rawValue = (params as { raw?: unknown }).raw; - if (typeof rawValue !== "string") { - respond( - false, - undefined, - errorShape( - ErrorCodes.INVALID_REQUEST, - "invalid config.apply params: raw (string) required", - ), - ); + const parsed = parseValidateConfigFromRawOrRespond(params, "config.apply", snapshot, respond); + if (!parsed) { return; } - const parsedRes = parseConfigJson5(rawValue); - if (!parsedRes.ok) { - respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, parsedRes.error)); - return; - } - const schemaApply = loadSchemaWithPlugins(); - const restored = restoreRedactedValues(parsedRes.parsed, snapshot.config, schemaApply.uiHints); - if (!restored.ok) { - respond( - false, - undefined, - errorShape(ErrorCodes.INVALID_REQUEST, restored.humanReadableMessage ?? "invalid config"), - ); - return; - } - const validated = validateConfigObjectWithPlugins(restored.result); - if (!validated.ok) { - respond( - false, - undefined, - errorShape(ErrorCodes.INVALID_REQUEST, "invalid config", { - details: { issues: validated.issues }, - }), - ); - return; - } - await writeConfigFile(validated.config, writeOptions); + await writeConfigFile(parsed.config, writeOptions); const { sessionKey, note, restartDelayMs, deliveryContext, threadId } = resolveConfigRestartRequest(params); @@ -479,7 +474,7 @@ export const configHandlers: GatewayRequestHandlers = { { ok: true, path: CONFIG_PATH, - config: redactConfigObject(validated.config, schemaApply.uiHints), + config: redactConfigObject(parsed.config, parsed.schema.uiHints), restart, sentinel: { path: sentinelPath,