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)