refactor(gateway): dedupe config raw validation

This commit is contained in:
Peter Steinberger
2026-02-15 06:02:50 +00:00
parent 628c7b2398
commit c46f395bb9

View File

@@ -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<ReturnType<typeof readConfigFileSnapshot>>,
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,