Files
openclaw/extensions/diffs
Peter Steinberger fa03d9b913 refactor: consolidate coercion helpers (#121366)
* refactor: consolidate coercion helpers

* fix: remove duplicate coercion imports

* fix: preserve serialized coercion guard

* chore: ratchet coercion helper carve-outs

* fix(test): keep gauntlet subprocess startup lean

* fix: preserve imported session timestamp semantics

* fix: preserve catalog timestamp string semantics

* chore: align plugin SDK surface ratchet

* fix: preserve trajectory and SDK string contracts

* fix(test): preserve QA record assertion semantics

* fix: complete standalone record guard rename

* refactor(cron): use canonical string coercion

* fix(acpx): preserve Pi timestamp parsing

* test(channels): adapt custody test harnesses

* test(telegram): classify media harness as test support

* test(acpx): split timestamp contract coverage

* test(channels): support generated custody contracts

* chore: ban the full coercion helper name set

Extends the declaration guard to all eleven consolidated helper names and
renames the cron schedule-identity readNumber wrapper to readScheduleInteger
so the banned generic name cannot regrow.

* fix(scripts): repair release-validation guard drift and lint cause

Restores the renamed isJsonRecord guard in assertTrustedWorkflowHarness after
main added isRecord call sites in parallel, and attaches the caught YAML error
as the thrown error cause (preserve-caught-error was red on main).

* fix: preserve Claude timestamp string semantics

* fix: preserve persisted timestamp string semantics

* fix: preserve date-first timestamp contracts

* fix(openai): harden delegation failure formatting

* chore: close coercion helper guard gaps

* test(openai): model non-error delegation rejection

* chore: refresh plugin SDK API contract

* fix(tasks): use canonical string field reader

* fix(ai): use canonical provider error field coercion

* fix(browser): migrate native bootstrap coercion

* docs(plugin-sdk): clarify text record export compatibility

* fix(gateway): normalize approval execution identity

* test(outbound): isolate message action poll harness
2026-08-11 00:02:18 -07:00
..
2026-06-04 21:02:07 -04:00

@openclaw/diffs

Read-only diff viewer plugin for OpenClaw agents.

Install

openclaw plugins install @openclaw/diffs

Restart the Gateway after installing or updating the plugin.

It gives agents one tool, diffs, that can:

  • render a gateway-hosted diff viewer for canvas use
  • render the same diff to a file (PNG or PDF)
  • accept either arbitrary before and after text or a unified patch

What Agents Get

The tool can return:

  • details.changed: false when before/after inputs are identical and no artifact was rendered; true for rendered results
  • details.viewerUrl: a gateway URL that can be opened in the canvas
  • details.filePath: a local rendered artifact path when file rendering is requested
  • details.fileFormat: the rendered file format (png or pdf)
  • details.artifactId and details.expiresAt: artifact identity and TTL metadata
  • details.context: available routing metadata such as agentId, sessionId, messageChannel, and agentAccountId

When the plugin is enabled, it also ships a companion skill from skills/ and prepends stable tool-usage guidance into system-prompt space via before_prompt_build. The hook uses prependSystemContext, so the guidance stays out of user-prompt space while still being available every turn.

This means an agent can:

  • call diffs with mode=view, then pass details.viewerUrl to canvas present
  • call diffs with mode=file, then send the file through the normal message tool using path or filePath
  • call diffs with mode=both when it wants both outputs

Tool Inputs

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"
}

Useful options:

  • mode: view, file, or both Deprecated alias: image behaves like file and is still accepted for backward compatibility.
  • layout: unified or split
  • theme: light or dark (default: dark)
  • fileFormat: png or pdf (default: png)
  • fileQuality: standard, hq, or print
  • fileScale: device scale override (1-4)
  • fileMaxWidth: max width override in CSS pixels (640-2400)
  • expandUnchanged: expand unchanged sections (per-call option only, not a plugin default key)
  • path: display name for before and after input
  • lang: language hint for before/after input; unknown values fall back to plain text
  • Default syntax highlighting covers common source, config, and documentation languages. Install diffs-language-pack for the extended language catalog.
  • title: explicit viewer title
  • ttlSeconds: artifact lifetime for viewer and standalone file outputs
  • baseUrl: override the gateway base URL used in the returned viewer link (origin or origin+base path only; no query/hash)
  • viewerBaseUrl plugin config: persistent fallback used when a tool call omits baseUrl

Input safety limits:

  • before and after: max 512 KiB each
  • patch: max 2 MiB
  • patch rendering cap: max 128 files / 120,000 lines

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",
            ttlSeconds: 21600,
          },
        },
      },
    },
  },
}

Explicit tool parameters still win over these defaults.

Docs

Package

  • Plugin id: diffs
  • Package: @openclaw/diffs
  • Minimum OpenClaw host: 2026.4.30

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
  • defaults.ttlSeconds (default 1800, max 21600): default artifact lifetime for viewer and standalone file outputs

Example:

{
  plugins: {
    entries: {
      diffs: {
        enabled: true,
        config: {
          viewerBaseUrl: "https://gateway.example.com/openclaw",
        },
      },
    },
  },
}

Example Agent Prompts

Open in canvas:

Use the `diffs` tool in `view` mode for this before and after content, then open the returned viewer URL in the canvas.

Path: docs/example.md

Before:
# Hello

This is version one.

After:
# Hello

This is version two.

Render a file (PNG or PDF):

Use the `diffs` tool in `file` mode for this before and after input. After it returns `details.filePath`, use the `message` tool with `path` or `filePath` to send me the rendered diff file.

Path: README.md

Before:
OpenClaw supports plugins.

After:
OpenClaw supports plugins and hosted diff views.

Do both:

Use the `diffs` tool in `both` mode for this diff. Open the viewer in the canvas and then send the rendered file by passing `details.filePath` to the `message` tool.

Path: src/demo.ts

Before:
const status = "old";

After:
const status = "new";

Patch input:

Use the `diffs` tool with this unified patch in `view` mode. After it returns the viewer URL, present it in the canvas.

diff --git a/src/example.ts b/src/example.ts
--- a/src/example.ts
+++ b/src/example.ts
@@ -1,3 +1,3 @@
 export function add(a: number, b: number) {
-  return a + b;
+  return a + b + 1;
 }

Notes

  • Multi-file patches start with a changed-files summary card: totals, per-file +N/-N stats, change badges, and anchor links.
  • Rendered PNG/PDF files keep the per-file header counts but omit the interactive view toggles.
  • The viewer is hosted locally through the gateway under /plugins/diffs/....
  • Viewer HTML and metadata are ephemeral SQLite plugin blobs. The URL token is returned to the caller while SQLite stores only its SHA-256 hash.
  • Rendered PNG/PDF files remain temporary materializations in $TMPDIR/openclaw-diffs because delivery APIs require a file path. No JSON metadata sidecars are written or imported.
  • Default viewer URLs use loopback (127.0.0.1) unless you set plugin viewerBaseUrl, pass baseUrl, or use gateway.bind=custom + gateway.customBindHost.
  • If gateway.trustedProxies includes loopback for a same-host proxy (for example Tailscale Serve), raw 127.0.0.1 viewer requests without forwarded client-IP headers fail closed by design.
  • In that topology, prefer mode=file / mode=both for attachments, or intentionally enable remote viewers and set plugin viewerBaseUrl (or pass a proxy/public baseUrl) when you need a shareable viewer URL.
  • Remote viewer misses are throttled to reduce token-guess abuse.
  • PNG or PDF rendering requires a Chromium-compatible browser. Set browser.executablePath if auto-detection is not enough.
  • If your delivery channel compresses images heavily (for example Telegram or WhatsApp), prefer fileFormat: "pdf" to preserve readability.
  • N unmodified lines rows may not always include expand controls for patch input, because many patch hunks do not carry full expandable context data.
  • Diff rendering is powered by Diffs.