docs(diffs): rewrite with Steps, Tabs, ParamField, and AccordionGroup for clearer mode and security guidance

This commit is contained in:
Vincent Koc
2026-04-25 23:11:13 -07:00
parent eb769ee4ec
commit 1b99f8aedb

View File

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