From 1b99f8aedb296d8ea252a5f956cbf0cad612d5ce Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Apr 2026 23:11:13 -0700 Subject: [PATCH] docs(diffs): rewrite with Steps, Tabs, ParamField, and AccordionGroup for clearer mode and security guidance --- docs/tools/diffs.md | 432 +++++++++++++++++++++++++------------------- 1 file changed, 246 insertions(+), 186 deletions(-) diff --git a/docs/tools/diffs.md b/docs/tools/diffs.md index 56e9914b8e9..856f7dc3aba 100644 --- a/docs/tools/diffs.md +++ b/docs/tools/diffs.md @@ -1,6 +1,7 @@ --- summary: "Read-only diff viewer and file renderer for agents (optional plugin tool)" title: "Diffs" +sidebarTitle: "Diffs" read_when: - You want agents to show code or markdown edits as diffs - You want a canvas-ready viewer URL or a rendered diff file @@ -24,24 +25,34 @@ When enabled, the plugin prepends concise usage guidance into system-prompt spac ## Quick start -1. Enable the plugin. -2. Call `diffs` with `mode: "view"` for canvas-first flows. -3. Call `diffs` with `mode: "file"` for chat file delivery flows. -4. Call `diffs` with `mode: "both"` when you need both artifacts. - -## Enable the plugin - -```json5 -{ - plugins: { - entries: { - diffs: { - enabled: true, + + + ```json5 + { + plugins: { + entries: { + diffs: { + enabled: true, + }, + }, }, - }, - }, -} -``` + } + ``` + + + + + Canvas-first flows: agents call `diffs` with `mode: "view"` and open `details.viewerUrl` with `canvas present`. + + + Chat file delivery: agents call `diffs` with `mode: "file"` and send `details.filePath` with `message` using `path` or `filePath`. + + + Combined: agents call `diffs` with `mode: "both"` to get both artifacts in one call. + + + + ## Disable built-in system guidance @@ -68,122 +79,174 @@ If you want to disable both the guidance and the tool, disable the plugin instea ## Typical agent workflow -1. Agent calls `diffs`. -2. Agent reads `details` fields. -3. Agent either: - - opens `details.viewerUrl` with `canvas present` - - sends `details.filePath` with `message` using `path` or `filePath` - - does both + + + Agent calls the `diffs` tool with input. + + + Agent reads `details` fields from the response. + + + Agent either opens `details.viewerUrl` with `canvas present`, sends `details.filePath` with `message` using `path` or `filePath`, or does both. + + ## Input examples -Before and after: - -```json -{ - "before": "# Hello\n\nOne", - "after": "# Hello\n\nTwo", - "path": "docs/example.md", - "mode": "view" -} -``` - -Patch: - -```json -{ - "patch": "diff --git a/src/example.ts b/src/example.ts\n--- a/src/example.ts\n+++ b/src/example.ts\n@@ -1 +1 @@\n-const x = 1;\n+const x = 2;\n", - "mode": "both" -} -``` + + + ```json + { + "before": "# Hello\n\nOne", + "after": "# Hello\n\nTwo", + "path": "docs/example.md", + "mode": "view" + } + ``` + + + ```json + { + "patch": "diff --git a/src/example.ts b/src/example.ts\n--- a/src/example.ts\n+++ b/src/example.ts\n@@ -1 +1 @@\n-const x = 1;\n+const x = 2;\n", + "mode": "both" + } + ``` + + ## Tool input reference -All fields are optional unless noted: +All fields are optional unless noted. -- `before` (`string`): original text. Required with `after` when `patch` is omitted. -- `after` (`string`): updated text. Required with `before` when `patch` is omitted. -- `patch` (`string`): unified diff text. Mutually exclusive with `before` and `after`. -- `path` (`string`): display filename for before and after mode. -- `lang` (`string`): language override hint for before and after mode. Unknown values fall back to plain text. -- `title` (`string`): viewer title override. -- `mode` (`"view" | "file" | "both"`): output mode. Defaults to plugin default `defaults.mode`. - Deprecated alias: `"image"` behaves like `"file"` and is still accepted for backward compatibility. -- `theme` (`"light" | "dark"`): viewer theme. Defaults to plugin default `defaults.theme`. -- `layout` (`"unified" | "split"`): diff layout. Defaults to plugin default `defaults.layout`. -- `expandUnchanged` (`boolean`): expand unchanged sections when full context is available. Per-call option only (not a plugin default key). -- `fileFormat` (`"png" | "pdf"`): rendered file format. Defaults to plugin default `defaults.fileFormat`. -- `fileQuality` (`"standard" | "hq" | "print"`): quality preset for PNG or PDF rendering. -- `fileScale` (`number`): device scale override (`1`-`4`). -- `fileMaxWidth` (`number`): max render width in CSS pixels (`640`-`2400`). -- `ttlSeconds` (`number`): artifact TTL in seconds for viewer and standalone file outputs. Default 1800, max 21600. -- `baseUrl` (`string`): viewer URL origin override. Overrides plugin `viewerBaseUrl`. Must be `http` or `https`, no query/hash. + + Original text. Required with `after` when `patch` is omitted. + + + Updated text. Required with `before` when `patch` is omitted. + + + Unified diff text. Mutually exclusive with `before` and `after`. + + + Display filename for before and after mode. + + + Language override hint for before and after mode. Unknown values fall back to plain text. + + + Viewer title override. + + + Output mode. Defaults to plugin default `defaults.mode`. Deprecated alias: `"image"` behaves like `"file"` and is still accepted for backward compatibility. + + + Viewer theme. Defaults to plugin default `defaults.theme`. + + + Diff layout. Defaults to plugin default `defaults.layout`. + + + Expand unchanged sections when full context is available. Per-call option only (not a plugin default key). + + + Rendered file format. Defaults to plugin default `defaults.fileFormat`. + + + Quality preset for PNG or PDF rendering. + + + Device scale override (`1`-`4`). + + + Max render width in CSS pixels (`640`-`2400`). + + + Artifact TTL in seconds for viewer and standalone file outputs. Max 21600. + + + Viewer URL origin override. Overrides plugin `viewerBaseUrl`. Must be `http` or `https`, no query/hash. + -Legacy input aliases still accepted for backward compatibility: + + + Still accepted for backward compatibility: -- `format` -> `fileFormat` -- `imageFormat` -> `fileFormat` -- `imageQuality` -> `fileQuality` -- `imageScale` -> `fileScale` -- `imageMaxWidth` -> `fileMaxWidth` + - `format` -> `fileFormat` + - `imageFormat` -> `fileFormat` + - `imageQuality` -> `fileQuality` + - `imageScale` -> `fileScale` + - `imageMaxWidth` -> `fileMaxWidth` -Validation and limits: - -- `before` and `after` each max 512 KiB. -- `patch` max 2 MiB. -- `path` max 2048 bytes. -- `lang` max 128 bytes. -- `title` max 1024 bytes. -- Patch complexity cap: max 128 files and 120000 total lines. -- `patch` and `before` or `after` together are rejected. -- Rendered file safety limits (apply to PNG and PDF): - - `fileQuality: "standard"`: max 8 MP (8,000,000 rendered pixels). - - `fileQuality: "hq"`: max 14 MP (14,000,000 rendered pixels). - - `fileQuality: "print"`: max 24 MP (24,000,000 rendered pixels). - - PDF also has a max of 50 pages. + + + - `before` and `after` each max 512 KiB. + - `patch` max 2 MiB. + - `path` max 2048 bytes. + - `lang` max 128 bytes. + - `title` max 1024 bytes. + - Patch complexity cap: max 128 files and 120000 total lines. + - `patch` and `before` or `after` together are rejected. + - Rendered file safety limits (apply to PNG and PDF): + - `fileQuality: "standard"`: max 8 MP (8,000,000 rendered pixels). + - `fileQuality: "hq"`: max 14 MP (14,000,000 rendered pixels). + - `fileQuality: "print"`: max 24 MP (24,000,000 rendered pixels). + - PDF also has a max of 50 pages. + + ## Output details contract The tool returns structured metadata under `details`. -Shared fields for modes that create a viewer: + + + Shared fields for modes that create a viewer: -- `artifactId` -- `viewerUrl` -- `viewerPath` -- `title` -- `expiresAt` -- `inputKind` -- `fileCount` -- `mode` -- `context` (`agentId`, `sessionId`, `messageChannel`, `agentAccountId` when available) + - `artifactId` + - `viewerUrl` + - `viewerPath` + - `title` + - `expiresAt` + - `inputKind` + - `fileCount` + - `mode` + - `context` (`agentId`, `sessionId`, `messageChannel`, `agentAccountId` when available) -File fields when PNG or PDF is rendered: + + + File fields when PNG or PDF is rendered: -- `artifactId` -- `expiresAt` -- `filePath` -- `path` (same value as `filePath`, for message tool compatibility) -- `fileBytes` -- `fileFormat` -- `fileQuality` -- `fileScale` -- `fileMaxWidth` + - `artifactId` + - `expiresAt` + - `filePath` + - `path` (same value as `filePath`, for message tool compatibility) + - `fileBytes` + - `fileFormat` + - `fileQuality` + - `fileScale` + - `fileMaxWidth` -Compatibility aliases also returned for existing callers: + + + Also returned for existing callers: -- `format` (same value as `fileFormat`) -- `imagePath` (same value as `filePath`) -- `imageBytes` (same value as `fileBytes`) -- `imageQuality` (same value as `fileQuality`) -- `imageScale` (same value as `fileScale`) -- `imageMaxWidth` (same value as `fileMaxWidth`) + - `format` (same value as `fileFormat`) + - `imagePath` (same value as `filePath`) + - `imageBytes` (same value as `fileBytes`) + - `imageQuality` (same value as `fileQuality`) + - `imageScale` (same value as `fileScale`) + - `imageMaxWidth` (same value as `fileMaxWidth`) + + + Mode behavior summary: -- `mode: "view"`: viewer fields only. -- `mode: "file"`: file fields only, no viewer artifact. -- `mode: "both"`: viewer fields plus file fields. If file rendering fails, viewer still returns with `fileError` and compatibility alias `imageError`. +| Mode | What is returned | +| -------- | ---------------------------------------------------------------------------------------------------------------------- | +| `"view"` | Viewer fields only. | +| `"file"` | File fields only, no viewer artifact. | +| `"both"` | Viewer fields plus file fields. If file rendering fails, viewer still returns with `fileError` and `imageError` alias. | ## Collapsed unchanged sections @@ -246,13 +309,11 @@ Supported defaults: Explicit tool parameters override these defaults. -Persistent viewer URL config: +### Persistent viewer URL config -- `viewerBaseUrl` (`string`, optional) - - Plugin-owned fallback for returned viewer links when a tool call does not pass `baseUrl`. - - Must be `http` or `https`, no query/hash. - -Example: + + Plugin-owned fallback for returned viewer links when a tool call does not pass `baseUrl`. Must be `http` or `https`, no query/hash. + ```json5 { @@ -271,11 +332,9 @@ Example: ## Security config -- `security.allowRemoteViewer` (`boolean`, default `false`) - - `false`: non-loopback requests to viewer routes are denied. - - `true`: remote viewers are allowed if tokenized path is valid. - -Example: + + `false`: non-loopback requests to viewer routes are denied. `true`: remote viewers are allowed if tokenized path is valid. + ```json5 { @@ -336,23 +395,24 @@ URL construction behavior: ## Security model -Viewer hardening: - -- Loopback-only by default. -- Tokenized viewer paths with strict ID and token validation. -- Viewer response CSP: - - `default-src 'none'` - - scripts and assets only from self - - no outbound `connect-src` -- Remote miss throttling when remote access is enabled: - - 40 failures per 60 seconds - - 60 second lockout (`429 Too Many Requests`) - -File rendering hardening: - -- Screenshot browser request routing is deny-by-default. -- Only local viewer assets from `http://127.0.0.1/plugins/diffs/assets/*` are allowed. -- External network requests are blocked. + + + - Loopback-only by default. + - Tokenized viewer paths with strict ID and token validation. + - Viewer response CSP: + - `default-src 'none'` + - scripts and assets only from self + - no outbound `connect-src` + - Remote miss throttling when remote access is enabled: + - 40 failures per 60 seconds + - 60 second lockout (`429 Too Many Requests`) + + + - Screenshot browser request routing is deny-by-default. + - Only local viewer assets from `http://127.0.0.1/plugins/diffs/assets/*` are allowed. + - External network requests are blocked. + + ## Browser requirements for file mode @@ -360,12 +420,19 @@ File rendering hardening: Resolution order: -1. `browser.executablePath` in OpenClaw config. -2. Environment variables: - - `OPENCLAW_BROWSER_EXECUTABLE_PATH` - - `BROWSER_EXECUTABLE_PATH` - - `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` -3. Platform command/path discovery fallback. + + + `browser.executablePath` in OpenClaw config. + + + - `OPENCLAW_BROWSER_EXECUTABLE_PATH` + - `BROWSER_EXECUTABLE_PATH` + - `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` + + + Platform command/path discovery fallback. + + Common failure text: @@ -375,42 +442,35 @@ Fix by installing Chrome, Chromium, Edge, or Brave, or setting one of the execut ## Troubleshooting -Input validation errors: - -- `Provide patch or both before and after text.` - - Include both `before` and `after`, or provide `patch`. -- `Provide either patch or before/after input, not both.` - - Do not mix input modes. -- `Invalid baseUrl: ...` - - Use `http(s)` origin with optional path, no query/hash. -- `{field} exceeds maximum size (...)` - - Reduce payload size. -- Large patch rejection - - Reduce patch file count or total lines. - -Viewer accessibility issues: - -- Viewer URL resolves to `127.0.0.1` by default. -- For remote access scenarios, either: - - set plugin `viewerBaseUrl`, or - - pass `baseUrl` per tool call, or - - use `gateway.bind=custom` and `gateway.customBindHost` -- If `gateway.trustedProxies` includes loopback for a same-host proxy (for example Tailscale Serve), raw loopback viewer requests without forwarded client-IP headers fail closed by design. -- For that proxy topology: - - prefer `mode: "file"` or `mode: "both"` when you only need an attachment, or - - intentionally enable `security.allowRemoteViewer` and set plugin `viewerBaseUrl` or pass a proxy/public `baseUrl` when you need a shareable viewer URL -- Enable `security.allowRemoteViewer` only when you intend external viewer access. - -Unmodified-lines row has no expand button: - -- This can happen for patch input when the patch does not carry expandable context. -- This is expected and does not indicate a viewer failure. - -Artifact not found: - -- Artifact expired due TTL. -- Token or path changed. -- Cleanup removed stale data. + + + - `Provide patch or both before and after text.` — include both `before` and `after`, or provide `patch`. + - `Provide either patch or before/after input, not both.` — do not mix input modes. + - `Invalid baseUrl: ...` — use `http(s)` origin with optional path, no query/hash. + - `{field} exceeds maximum size (...)` — reduce payload size. + - Large patch rejection — reduce patch file count or total lines. + + + - Viewer URL resolves to `127.0.0.1` by default. + - For remote access scenarios, either: + - set plugin `viewerBaseUrl`, or + - pass `baseUrl` per tool call, or + - use `gateway.bind=custom` and `gateway.customBindHost` + - If `gateway.trustedProxies` includes loopback for a same-host proxy (for example Tailscale Serve), raw loopback viewer requests without forwarded client-IP headers fail closed by design. + - For that proxy topology: + - prefer `mode: "file"` or `mode: "both"` when you only need an attachment, or + - intentionally enable `security.allowRemoteViewer` and set plugin `viewerBaseUrl` or pass a proxy/public `baseUrl` when you need a shareable viewer URL + - Enable `security.allowRemoteViewer` only when you intend external viewer access. + + + This can happen for patch input when the patch does not carry expandable context. This is expected and does not indicate a viewer failure. + + + - Artifact expired due TTL. + - Token or path changed. + - Cleanup removed stale data. + + ## Operational guidance @@ -421,12 +481,12 @@ Artifact not found: - Avoid sending secrets in diff input when not required. - If your channel compresses images aggressively (for example Telegram or WhatsApp), prefer PDF output (`fileFormat: "pdf"`). -Diff rendering engine: + +Diff rendering engine powered by [Diffs](https://diffs.com). + -- Powered by [Diffs](https://diffs.com). +## Related -## Related docs - -- [Tools overview](/tools) -- [Plugins](/tools/plugin) - [Browser](/tools/browser) +- [Plugins](/tools/plugin) +- [Tools overview](/tools)