Diffs

diffs es una herramienta de plugin opcional con guía breve integrada en el sistema y un skill complementario que convierte el contenido de cambios en un artefacto de diff de solo lectura para agentes.

Acepta:

  • texto before y after
  • un patch unificado

Puede devolver:

  • una URL del visor del gateway para presentación en canvas
  • una ruta de archivo renderizado (PNG o PDF) para entrega en mensajes
  • ambas salidas en una sola llamada

Cuando está habilitado, el plugin antepone una guía de uso concisa en el espacio de prompt del sistema y también expone un skill detallado para casos donde el agente necesita instrucciones más completas.

Inicio rápido

  1. Habilita el plugin.
  2. Llama a diffs con mode: "view" para flujos centrados en canvas.
  3. Llama a diffs con mode: "file" para flujos de entrega de archivos en chat.
  4. Llama a diffs con mode: "both" cuando necesites ambos artefactos.

Habilitar el plugin

{
  plugins: {
    entries: {
      diffs: {
        enabled: true,
      },
    },
  },
}

Deshabilitar la guía integrada del sistema

Si quieres mantener la herramienta diffs habilitada pero deshabilitar su guía integrada del prompt del sistema, establece plugins.entries.diffs.hooks.allowPromptInjection a false:

{
  plugins: {
    entries: {
      diffs: {
        enabled: true,
        hooks: {
          allowPromptInjection: false,
        },
      },
    },
  },
}

Esto bloquea el hook before_prompt_build del plugin diffs mientras mantiene disponibles el plugin, la herramienta y el skill complementario.

Si quieres deshabilitar tanto la guía como la herramienta, deshabilita el plugin en su lugar.

Flujo de trabajo típico del agente

  1. El agente llama a diffs.
  2. El agente lee los campos details.
  3. El agente puede:
    • abrir details.viewerUrl con canvas present
    • enviar details.filePath con message usando path o filePath
    • hacer ambas cosas

Ejemplos de entrada

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

Referencia de entrada de la herramienta

Todos los campos son opcionales a menos que se indique:

  • before (string): texto original. Requerido con after cuando se omite patch.
  • after (string): texto actualizado. Requerido con before cuando se omite patch.
  • patch (string): texto de diff unificado. Mutuamente excluyente con before y after.
  • path (string): nombre de archivo para mostrar en modo before y after.
  • lang (string): pista de sobrecarga de lenguaje para modo before y after.
  • title (string): sobrecarga del título del visor.
  • mode ("view" | "file" | "both"): modo de salida. Por defecto usa el valor del plugin defaults.mode.
  • theme ("light" | "dark"): tema del visor. Por defecto usa el valor del plugin defaults.theme.
  • layout ("unified" | "split"): diseño del diff. Por defecto usa el valor del plugin defaults.layout.
  • expandUnchanged (boolean): expandir secciones sin cambios cuando el contexto completo está disponible. Opción solo por llamada (no es una clave por defecto del plugin).
  • fileFormat ("png" | "pdf"): formato del archivo renderizado. Por defecto usa el valor del plugin defaults.fileFormat.
  • fileQuality ("standard" | "hq" | "print"): preset de calidad para renderizado PNG o PDF.
  • fileScale (number): sobrecarga de escala de dispositivo (1-4).
  • fileMaxWidth (number): ancho máximo de renderizado en píxeles CSS (640-2400).
  • ttlSeconds (number): TTL del artefacto del visor en segundos. Por defecto 1800, máximo 21600.
  • baseUrl (string): sobrecarga del origen de la URL del visor. Debe ser http o https, sin query/hash.

Validación y límites:

  • before y after máximo 512 KiB cada uno.
  • patch máximo 2 MiB.
  • path máximo 2048 bytes.
  • lang máximo 128 bytes.
  • title máximo 1024 bytes.
  • Límite de complejidad del patch: máximo 128 archivos y 120000 líneas totales.
  • patch y before o after juntos se rechazan.
  • Límites de seguridad del archivo renderizado (aplican a PNG y PDF):
    • fileQuality: "standard": máximo 8 MP (8,000,000 píxeles renderizados).
    • fileQuality: "hq": máximo 14 MP (14,000,000 píxeles renderizados).
    • fileQuality: "print": máximo 24 MP (24,000,000 píxeles renderizados).
    • PDF también tiene un máximo de 50 páginas.

Contrato de detalles de salida

La herramienta devuelve metadatos estructurados bajo details.

Campos compartidos para modos que crean un visor:

  • artifactId
  • viewerUrl
  • viewerPath
  • title
  • expiresAt
  • inputKind
  • fileCount
  • mode

Campos de archivo cuando se renderiza PNG o PDF:

  • filePath
  • path (mismo valor que filePath, para compatibilidad con la herramienta message)
  • fileBytes
  • fileFormat
  • fileQuality
  • fileScale
  • fileMaxWidth

Resumen de comportamiento por modo:

  • mode: "view": solo campos del visor.
  • mode: "file": solo campos de archivo, sin artefacto del visor.
  • mode: "both": campos del visor más campos de archivo. Si el renderizado de archivo falla, el visor aún se devuelve con fileError.

Secciones sin cambios colapsadas

  • El visor puede mostrar filas como N unmodified lines.
  • Los controles de expansión en esas filas son condicionales y no están garantizados para cada tipo de entrada.
  • Los controles de expansión aparecen cuando el diff renderizado tiene datos de contexto expandibles, lo cual es típico para entrada de before y after.
  • Para muchas entradas de patch unificado, los cuerpos de contexto omitidos no están disponibles en los hunks del patch analizado, así que la fila puede aparecer sin controles de expansión. Este es el comportamiento esperado.
  • expandUnchanged aplica solo cuando existe contexto expandible.

Valores por defecto del plugin

Establece valores por defecto a nivel de plugin en ~/.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",
          },
        },
      },
    },
  },
}

Valores por defecto soportados:

  • fontFamily
  • fontSize
  • lineSpacing
  • layout
  • showLineNumbers
  • diffIndicators
  • wordWrap
  • background
  • theme
  • fileFormat
  • fileQuality
  • fileScale
  • fileMaxWidth
  • mode

Los parámetros explícitos de la herramienta sobrecargan estos valores por defecto.

Configuración de seguridad

  • security.allowRemoteViewer (boolean, por defecto false)
    • false: las solicitudes no-loopback a rutas del visor se deniegan.
    • true: los visores remotos se permiten si la ruta tokenizada es válida.

Ejemplo:

{
  plugins: {
    entries: {
      diffs: {
        enabled: true,
        config: {
          security: {
            allowRemoteViewer: false,
          },
        },
      },
    },
  },
}

Ciclo de vida y almacenamiento de artefactos

  • Los artefactos se almacenan bajo la subcarpeta temporal: $TMPDIR/openclaw-diffs.
  • Los metadatos del artefacto del visor contienen:
    • ID de artefacto aleatorio (20 caracteres hex)
    • token aleatorio (48 caracteres hex)
    • createdAt y expiresAt
    • ruta almacenada de viewer.html
  • El TTL por defecto del visor es 30 minutos cuando no se especifica.
  • El TTL máximo aceptado del visor es 6 horas.
  • La limpieza se ejecuta oportunistamente después de la creación de artefactos.
  • Los artefactos expirados se eliminan.
  • La limpieza de respaldo elimina carpetas obsoletas con más de 24 horas cuando faltan los metadatos.

URL del visor y comportamiento de red

Ruta del visor:

  • /plugins/diffs/view/{artifactId}/{token}

Assets del visor:

  • /plugins/diffs/assets/viewer.js
  • /plugins/diffs/assets/viewer-runtime.js

Comportamiento de construcción de URL:

  • Si se proporciona baseUrl, se usa después de una validación estricta.
  • Sin baseUrl, la URL del visor usa por defecto loopback 127.0.0.1.
  • Si el modo de vinculación del gateway es custom y gateway.customBindHost está establecido, se usa ese host.

Reglas de baseUrl:

  • Debe ser http:// o https://.
  • Se rechazan query y hash.
  • Se permite origen más ruta base opcional.

Modelo de seguridad

Endurecimiento del visor:

  • Solo loopback por defecto.
  • Rutas del visor tokenizadas con validación estricta de ID y token.
  • CSP de la respuesta del visor:
    • default-src 'none'
    • scripts y assets solo desde self
    • sin connect-src de salida
  • Throttling de errores remotos cuando el acceso remoto está habilitado:
    • 40 fallos por 60 segundos
    • 60 segundos de bloqueo (429 Too Many Requests)

Endurecimiento del renderizado de archivos:

  • El enrutamiento de solicitudes del navegador para capturas de pantalla es deny-by-default.
  • Solo se permiten assets locales del visor desde http://127.0.0.1/plugins/diffs/assets/*.
  • Las solicitudes de red externas se bloquean.

Requisitos del navegador para modo file

mode: "file" y mode: "both" necesitan un navegador compatible con Chromium.

Orden de resolución:

  1. browser.executablePath en la configuración de OpenClaw.
  2. Variables de entorno:
    • OPENCLAW_BROWSER_EXECUTABLE_PATH
    • BROWSER_EXECUTABLE_PATH
    • PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
  3. Respaldo de descubrimiento por comando/ruta de la plataforma.

Texto de error común:

  • Diff PNG/PDF rendering requires a Chromium-compatible browser...

Solución: instala Chrome, Chromium, Edge o Brave, o establece una de las opciones de ruta ejecutable de arriba.

Solución de problemas

Errores de validación de entrada:

  • Provide patch or both before and after text.
    • Incluye tanto before como after, o proporciona patch.
  • Provide either patch or before/after input, not both.
    • No mezcles modos de entrada.
  • Invalid baseUrl: ...
    • Usa origen http(s) con ruta opcional, sin query/hash.
  • {field} exceeds maximum size (...)
    • Reduce el tamaño de la carga útil.
  • Rechazo de patch grande
    • Reduce el conteo de archivos del patch o las líneas totales.

Problemas de accesibilidad del visor:

  • La URL del visor resuelve a 127.0.0.1 por defecto.
  • Para escenarios de acceso remoto:
    • pasa baseUrl por llamada a la herramienta, o
    • usa gateway.bind=custom y gateway.customBindHost
  • Habilita security.allowRemoteViewer solo cuando pretendas acceso externo al visor.

La fila de líneas sin modificar no tiene botón de expansión:

  • Esto puede ocurrir para entrada de patch cuando el patch no lleva contexto expandible.
  • Este es el comportamiento esperado y no indica un fallo del visor.

Artefacto no encontrado:

  • El artefacto expiró por TTL.
  • El token o la ruta cambió.
  • La limpieza eliminó datos obsoletos.

Guía operacional

  • Prefiere mode: "view" para revisiones interactivas locales en canvas.
  • Prefiere mode: "file" para canales de chat salientes que necesitan un adjunto.
  • Mantén allowRemoteViewer deshabilitado a menos que tu despliegue requiera URLs de visor remotas.
  • Establece un ttlSeconds corto explícito para diffs sensibles.
  • Evita enviar secretos en la entrada de diffs cuando no sea necesario.
  • Si tu canal comprime imágenes agresivamente (por ejemplo Telegram o WhatsApp), prefiere salida PDF (fileFormat: "pdf").

Motor de renderizado de diffs:

Documentación relacionada