Tools
Diffs
diffs is an optional plugin tool with short built-in system guidance and a companion skill that turns change content into a read-only diff artifact for agents.
It accepts either:
beforeandaftertext- a unified
patch
It can return:
- a gateway viewer URL for canvas presentation
- a rendered file path (PNG or PDF) for message delivery
- both outputs in one call
When enabled, the plugin prepends concise usage guidance into system-prompt space and also exposes a detailed skill for cases where the agent needs fuller instructions.
Quick start
Install the plugin
openclaw plugins install diffs
Enable the plugin
{
plugins: {
entries: {
diffs: {
enabled: true,
},
},
},
}
Pick a mode
view
Canvas-first flows: agents call diffs with mode: "view" and open details.viewerUrl with canvas present.
file
Chat file delivery: agents call diffs with mode: "file" and send details.filePath with message using path or filePath.
both
Combined: agents call diffs with mode: "both" to get both artifacts in one call.
Disable built-in system guidance
If you want to keep the diffs tool enabled but disable its built-in system-prompt guidance, set plugins.entries.diffs.hooks.allowPromptInjection to false:
{
plugins: {
entries: {
diffs: {
enabled: true,
hooks: {
allowPromptInjection: false,
},
},
},
},
}
This blocks the diffs plugin's before_prompt_build hook while keeping the plugin, tool, and companion skill available.
If you want to disable both the guidance and the tool, disable the plugin instead.
Typical agent workflow
Call diffs
Agent calls the diffs tool with input.
Read details
Agent reads details fields from the response.
Present
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
{
"before": "# Hello\n\nOne",
"after": "# Hello\n\nTwo",
"path": "docs/example.md",
"mode": "view"
}
Patch
{
"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.
beforestringOriginal text. Required with after when patch is omitted.
afterstringUpdated text. Required with before when patch is omitted.
patchstringUnified diff text. Mutually exclusive with before and after.
pathstringDisplay filename for before and after mode.
langstringLanguage override hint for before and after mode. Unknown values fall back to plain text.
titlestringViewer 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.
expandUnchangedbooleanExpand 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.
fileScalenumberDevice scale override (1-4).
fileMaxWidthnumberMax render width in CSS pixels (640-2400).
ttlSecondsnumberArtifact TTL in seconds for viewer and standalone file outputs. Max 21600.
baseUrlstringViewer URL origin override. Overrides plugin viewerBaseUrl. Must be http or https, no query/hash.
Legacy input aliases
Still accepted for backward compatibility:
format->fileFormatimageFormat->fileFormatimageQuality->fileQualityimageScale->fileScaleimageMaxWidth->fileMaxWidth
Validation and limits
beforeandaftereach max 512 KiB.patchmax 2 MiB.pathmax 2048 bytes.langmax 128 bytes.titlemax 1024 bytes.- Patch complexity cap: max 128 files and 120000 total lines.
patchandbeforeoraftertogether 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.
Viewer fields
Shared fields for modes that create a viewer:
artifactIdviewerUrlviewerPathtitleexpiresAtinputKindfileCountmodecontext(agentId,sessionId,messageChannel,agentAccountIdwhen available)
File fields
File fields when PNG or PDF is rendered:
artifactIdexpiresAtfilePathpath(same value asfilePath, for message tool compatibility)fileBytesfileFormatfileQualityfileScalefileMaxWidth
Compatibility aliases
Also returned for existing callers:
format(same value asfileFormat)imagePath(same value asfilePath)imageBytes(same value asfileBytes)imageQuality(same value asfileQuality)imageScale(same value asfileScale)imageMaxWidth(same value asfileMaxWidth)
Mode behavior summary:
| 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
- The viewer can show rows like
N unmodified lines. - Expand controls on those rows are conditional and not guaranteed for every input kind.
- Expand controls appear when the rendered diff has expandable context data, which is typical for before and after input.
- For many unified patch inputs, omitted context bodies are not available in the parsed patch hunks, so the row can appear without expand controls. This is expected behavior.
expandUnchangedapplies only when expandable context exists.
Plugin defaults
Set plugin-wide defaults in ~/.openclaw/openclaw.json:
{
plugins: {
entries: {
diffs: {
enabled: true,
config: {
defaults: {
fontFamily: "Fira Code",
fontSize: 15,
lineSpacing: 1.6,
layout: "unified",
showLineNumbers: true,
diffIndicators: "bars",
wordWrap: true,
background: true,
theme: "dark",
fileFormat: "png",
fileQuality: "standard",
fileScale: 2,
fileMaxWidth: 960,
mode: "both",
},
},
},
},
},
}
Supported defaults:
fontFamilyfontSizelineSpacinglayoutshowLineNumbersdiffIndicatorswordWrapbackgroundthemefileFormatfileQualityfileScalefileMaxWidthmode
Explicit tool parameters override these defaults.
Persistent viewer URL config
viewerBaseUrlstringPlugin-owned fallback for returned viewer links when a tool call does not pass baseUrl. Must be http or https, no query/hash.
{
plugins: {
entries: {
diffs: {
enabled: true,
config: {
viewerBaseUrl: "https://gateway.example.com/openclaw",
},
},
},
},
}
Security config
security.allowRemoteViewerbooleanfalse: non-loopback requests to viewer routes are denied. true: remote viewers are allowed if tokenized path is valid.
{
plugins: {
entries: {
diffs: {
enabled: true,
config: {
security: {
allowRemoteViewer: false,
},
},
},
},
},
}
Artifact lifecycle and storage
- Artifacts are stored under the temp subfolder:
$TMPDIR/openclaw-diffs. - Viewer artifact metadata contains:
- random artifact ID (20 hex chars)
- random token (48 hex chars)
createdAtandexpiresAt- stored
viewer.htmlpath
- Default artifact TTL is 30 minutes when not specified.
- Maximum accepted viewer TTL is 6 hours.
- Cleanup runs opportunistically after artifact creation.
- Expired artifacts are deleted.
- Fallback cleanup removes stale folders older than 24 hours when metadata is missing.
Viewer URL and network behavior
Viewer route:
/plugins/diffs/view/{artifactId}/{token}
Viewer assets:
/plugins/diffs/assets/viewer.js/plugins/diffs/assets/viewer-runtime.js
The viewer document resolves those assets relative to the viewer URL, so an optional baseUrl path prefix is preserved for both asset requests too.
URL construction behavior:
- If tool-call
baseUrlis provided, it is used after strict validation. - Else if plugin
viewerBaseUrlis configured, it is used. - Without either override, viewer URL defaults to loopback
127.0.0.1. - If gateway bind mode is
customandgateway.customBindHostis set, that host is used.
baseUrl rules:
- Must be
http://orhttps://. - Query and hash are rejected.
- Origin plus optional base path is allowed.
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.
Browser requirements for file mode
mode: "file" and mode: "both" need a Chromium-compatible browser.
Resolution order:
Config
browser.executablePath in OpenClaw config.
Environment variables
OPENCLAW_BROWSER_EXECUTABLE_PATHBROWSER_EXECUTABLE_PATHPLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
Platform fallback
Platform command/path discovery fallback.
Common failure text:
Diff PNG/PDF rendering requires a Chromium-compatible browser...
Fix by installing Chrome, Chromium, Edge, or Brave, or setting one of the executable path options above.
Troubleshooting
Input validation errors
Provide patch or both before and after text.— include bothbeforeandafter, or providepatch.Provide either patch or before/after input, not both.— do not mix input modes.Invalid baseUrl: ...— usehttp(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
- Viewer URL resolves to
127.0.0.1by default. - For remote access scenarios, either:
- set plugin
viewerBaseUrl, or - pass
baseUrlper tool call, or - use
gateway.bind=customandgateway.customBindHost
- set plugin
- If
gateway.trustedProxiesincludes 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"ormode: "both"when you only need an attachment, or - intentionally enable
security.allowRemoteViewerand set pluginviewerBaseUrlor pass a proxy/publicbaseUrlwhen you need a shareable viewer URL
- prefer
- Enable
security.allowRemoteVieweronly 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.
Operational guidance
- Prefer
mode: "view"for local interactive reviews in canvas. - Prefer
mode: "file"for outbound chat channels that need an attachment. - Keep
allowRemoteViewerdisabled unless your deployment requires remote viewer URLs. - Set explicit short
ttlSecondsfor sensitive diffs. - 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").