From 45bdaa2f7b266604f0562c94bb0c1dcb8d5d8377 Mon Sep 17 00:00:00 2001 From: ksj3421 Date: Mon, 1 Jun 2026 11:10:02 +0900 Subject: [PATCH] fix(agents): return schema lookup misses in-band Return unknown config.schema.lookup paths as an in-band agent gateway tool result instead of throwing into channel warning surfaces. The direct gateway RPC still reports INVALID_REQUEST, preserving the existing protocol contract, while the agent-facing gateway tool returns schema_path_not_found for exploratory misses. Fixes #88813. Thanks @ksj3421. Reported by @cjalden. --- src/agents/openclaw-gateway-tool.test.ts | 26 ++++++++++++++++++++++++ src/agents/tools/gateway-tool.ts | 26 ++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/agents/openclaw-gateway-tool.test.ts b/src/agents/openclaw-gateway-tool.test.ts index 51474d56919..a60aa4e77b3 100644 --- a/src/agents/openclaw-gateway-tool.test.ts +++ b/src/agents/openclaw-gateway-tool.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import { GatewayClientRequestError } from "../gateway/client.js"; import { testing as restartTesting } from "../infra/restart.js"; import { withEnvAsync } from "../test-utils/env.js"; import { createGatewayTool } from "./tools/gateway-tool.js"; @@ -725,4 +726,29 @@ describe("gateway tool", () => { ?.schema; expect(schema?.properties).toBeUndefined(); }); + + it("returns an in-band schema lookup miss for unknown paths", async () => { + vi.mocked(callGatewayTool).mockRejectedValueOnce( + new GatewayClientRequestError({ + code: "INVALID_REQUEST", + message: "config schema path not found", + }), + ); + const tool = requireGatewayTool(); + + const result = await tool.execute("call6", { + action: "config.schema.lookup", + path: "agents.main.authorizedSenders", + }); + + expect(gatewayCall("config.schema.lookup")[2]).toEqual({ + path: "agents.main.authorizedSenders", + }); + expect(result.details).toEqual({ + ok: false, + code: "schema_path_not_found", + path: "agents.main.authorizedSenders", + message: "config schema path not found", + }); + }); }); diff --git a/src/agents/tools/gateway-tool.ts b/src/agents/tools/gateway-tool.ts index 8ed4c45c31b..f9f01ac5f93 100644 --- a/src/agents/tools/gateway-tool.ts +++ b/src/agents/tools/gateway-tool.ts @@ -10,6 +10,7 @@ import { parseConfigJson5, resolveConfigSnapshotHash } from "../../config/io.js" import { applyMergePatch } from "../../config/merge-patch.js"; import { extractDeliveryInfo } from "../../config/sessions.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; +import { GatewayClientRequestError } from "../../gateway/client.js"; import { buildRestartSuccessContinuation, formatDoctorNonInteractiveHint, @@ -33,6 +34,7 @@ import { callGatewayTool, readGatewayCallOptions } from "./gateway.js"; const log = createSubsystemLogger("gateway-tool"); const DEFAULT_UPDATE_TIMEOUT_MS = 20 * 60_000; +const CONFIG_SCHEMA_PATH_NOT_FOUND_MESSAGE = "config schema path not found"; // Per SECURITY.md the model/agent itself is not a trusted principal. // `assertGatewayConfigMutationAllowed` is the explicit model -> operator // trust-boundary control on `config.apply`/`config.patch`, so the runtime tool @@ -114,6 +116,14 @@ function stripConfigWriteResultPayload(result: unknown): unknown { return stripped; } +function isConfigSchemaPathNotFoundError(error: unknown): boolean { + return ( + error instanceof GatewayClientRequestError && + error.gatewayCode === "INVALID_REQUEST" && + error.message.includes(CONFIG_SCHEMA_PATH_NOT_FOUND_MESSAGE) + ); +} + function parseGatewayConfigMutationRaw( raw: string, action: "config.apply" | "config.patch", @@ -475,8 +485,20 @@ export function createGatewayTool(opts?: { required: true, label: "path", }); - const result = await callGatewayTool("config.schema.lookup", gatewayOpts, { path }); - return jsonResult({ ok: true, result }); + try { + const result = await callGatewayTool("config.schema.lookup", gatewayOpts, { path }); + return jsonResult({ ok: true, result }); + } catch (error) { + if (isConfigSchemaPathNotFoundError(error)) { + return jsonResult({ + ok: false, + code: "schema_path_not_found", + path, + message: CONFIG_SCHEMA_PATH_NOT_FOUND_MESSAGE, + }); + } + throw error; + } } if (action === "config.apply") { const { raw, baseHash, snapshotConfig, sessionKey, note, restartDelayMs } =