diff --git a/docs/tools/diffs.md b/docs/tools/diffs.md index ed41ae690b9..31ccdfbfa86 100644 --- a/docs/tools/diffs.md +++ b/docs/tools/diffs.md @@ -237,6 +237,23 @@ Persistent viewer URL config: - Plugin-owned fallback for returned viewer links when a tool call does not pass `baseUrl`. - Must be `http` or `https`, no query/hash. +Example: + +```json5 +{ + plugins: { + entries: { + diffs: { + enabled: true, + config: { + viewerBaseUrl: "https://gateway.example.com/openclaw", + }, + }, + }, + }, +} +``` + ## Security config - `security.allowRemoteViewer` (`boolean`, default `false`) diff --git a/extensions/diffs/README.md b/extensions/diffs/README.md index 056818e8b28..31a5e82c93e 100644 --- a/extensions/diffs/README.md +++ b/extensions/diffs/README.md @@ -112,6 +112,23 @@ Security options: - `security.allowRemoteViewer` (default `false`): allows non-loopback access to `/plugins/diffs/view/...` token URLs - `viewerBaseUrl` (optional): persistent viewer-link origin/path fallback for shareable URLs +Example: + +```json5 +{ + plugins: { + entries: { + diffs: { + enabled: true, + config: { + viewerBaseUrl: "https://gateway.example.com/openclaw", + }, + }, + }, + }, +} +``` + ## Example Agent Prompts Open in canvas: diff --git a/extensions/diffs/src/config.test.ts b/extensions/diffs/src/config.test.ts index 8e0dc295f06..fbd740a6246 100644 --- a/extensions/diffs/src/config.test.ts +++ b/extensions/diffs/src/config.test.ts @@ -305,7 +305,7 @@ describe("diffs plugin schema surfaces", () => { issues: [ { path: ["viewerBaseUrl"], - message: "baseUrl must use http or https: javascript:alert(1)", + message: "viewerBaseUrl must use http or https: javascript:alert(1)", }, ], }, @@ -382,6 +382,12 @@ describe("diffs viewer URL helpers", () => { "baseUrl must not include query/hash", ); }); + + it("uses the configured field name in viewerBaseUrl validation errors", () => { + expect(() => normalizeViewerBaseUrl("https://example.com?a=1", "viewerBaseUrl")).toThrow( + "viewerBaseUrl must not include query/hash", + ); + }); }); describe("renderDiffDocument", () => { diff --git a/extensions/diffs/src/config.ts b/extensions/diffs/src/config.ts index a806e45e922..dd67e19afc4 100644 --- a/extensions/diffs/src/config.ts +++ b/extensions/diffs/src/config.ts @@ -100,7 +100,7 @@ const DiffsPluginJsonSchemaSource = z.strictObject({ .string() .superRefine((value, ctx) => { try { - normalizeViewerBaseUrl(value); + normalizeViewerBaseUrl(value, "viewerBaseUrl"); } catch (error) { ctx.addIssue({ code: "custom", diff --git a/extensions/diffs/src/url.ts b/extensions/diffs/src/url.ts index e8c40e05753..5b3e29b20ff 100644 --- a/extensions/diffs/src/url.ts +++ b/extensions/diffs/src/url.ts @@ -1,6 +1,7 @@ import type { OpenClawConfig } from "../api.js"; const DEFAULT_GATEWAY_PORT = 18789; +type ViewerBaseUrlFieldName = "baseUrl" | "viewerBaseUrl"; export function buildViewerUrl(params: { config: OpenClawConfig; @@ -20,18 +21,21 @@ export function buildViewerUrl(params: { return parsedBase.toString(); } -export function normalizeViewerBaseUrl(raw: string): string { +export function normalizeViewerBaseUrl( + raw: string, + fieldName: ViewerBaseUrlFieldName = "baseUrl", +): string { let parsed: URL; try { parsed = new URL(raw); } catch { - throw new Error(`Invalid baseUrl: ${raw}`); + throw new Error(`Invalid ${fieldName}: ${raw}`); } if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { - throw new Error(`baseUrl must use http or https: ${raw}`); + throw new Error(`${fieldName} must use http or https: ${raw}`); } if (parsed.search || parsed.hash) { - throw new Error(`baseUrl must not include query/hash: ${raw}`); + throw new Error(`${fieldName} must not include query/hash: ${raw}`); } parsed.search = ""; parsed.hash = "";