메인 콘텐츠로 건너뛰기

Diffs

diffs는 짧은 내장 시스템 안내와 함께, 에이전트용 변경 내용을 읽기 전용 diff 아티팩트로 바꿔 주는 보조 skill을 제공하는 선택적 플러그인 도구입니다. 다음 중 하나를 입력으로 받을 수 있습니다:
  • beforeafter 텍스트
  • unified patch
다음 중 하나를 반환할 수 있습니다:
  • 캔버스 표시를 위한 게이트웨이 뷰어 URL
  • 메시지 전달용 렌더링 파일 경로(PNG 또는 PDF)
  • 한 번의 호출로 두 출력 모두
활성화되면, 이 플러그인은 간결한 사용 안내를 system-prompt 공간 앞부분에 주입하고, 에이전트가 더 자세한 지침이 필요할 때 사용할 수 있도록 상세 skill도 함께 제공합니다.

빠른 시작

  1. 플러그인을 활성화합니다.
  2. 캔버스 우선 흐름에는 mode: "view"diffs를 호출합니다.
  3. 채팅 파일 전달 흐름에는 mode: "file"diffs를 호출합니다.
  4. 두 아티팩트가 모두 필요하면 mode: "both"diffs를 호출합니다.

플러그인 활성화

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

내장 시스템 안내 비활성화

diffs 도구는 활성화한 상태로 유지하되, 내장 system-prompt 안내만 비활성화하려면 plugins.entries.diffs.hooks.allowPromptInjectionfalse로 설정합니다:
{
  plugins: {
    entries: {
      diffs: {
        enabled: true,
        hooks: {
          allowPromptInjection: false,
        },
      },
    },
  },
}
이렇게 하면 diffs 플러그인의 before_prompt_build hook은 차단되지만, 플러그인, 도구, 보조 skill은 계속 사용할 수 있습니다. 안내와 도구를 모두 비활성화하려면, 대신 플러그인 자체를 비활성화하면 됩니다.

일반적인 에이전트 워크플로

  1. 에이전트가 diffs를 호출합니다.
  2. 에이전트가 details 필드를 읽습니다.
  3. 에이전트는 다음 중 하나를 수행합니다:
    • canvas presentdetails.viewerUrl을 엽니다
    • path 또는 filePath를 사용해 messagedetails.filePath를 전송합니다
    • 둘 다 수행합니다

입력 예시

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

도구 입력 참조

별도 표기가 없는 한 모든 필드는 선택 사항입니다:
  • before (string): 원본 텍스트. patch가 생략된 경우 after와 함께 필수입니다.
  • after (string): 수정된 텍스트. patch가 생략된 경우 before와 함께 필수입니다.
  • patch (string): unified diff 텍스트. before, after와 상호 배타적입니다.
  • path (string): before/after 모드에서 표시할 파일명입니다.
  • lang (string): before/after 모드용 언어 override 힌트입니다.
  • title (string): 뷰어 제목 override입니다.
  • mode ("view" | "file" | "both"): 출력 모드입니다. 기본값은 플러그인 기본값 defaults.mode입니다.
  • theme ("light" | "dark"): 뷰어 테마입니다. 기본값은 플러그인 기본값 defaults.theme입니다.
  • layout ("unified" | "split"): diff 레이아웃입니다. 기본값은 플러그인 기본값 defaults.layout입니다.
  • expandUnchanged (boolean): 전체 컨텍스트를 사용할 수 있을 때 변경되지 않은 구간을 펼칩니다. 호출 단위 옵션만 지원합니다(플러그인 기본값 키는 아님).
  • fileFormat ("png" | "pdf"): 렌더링 파일 형식입니다. 기본값은 플러그인 기본값 defaults.fileFormat입니다.
  • fileQuality ("standard" | "hq" | "print"): PNG 또는 PDF 렌더링용 품질 프리셋입니다.
  • fileScale (number): device scale override입니다(1-4).
  • fileMaxWidth (number): CSS 픽셀 기준 최대 렌더링 너비입니다(640-2400).
  • ttlSeconds (number): 뷰어 아티팩트 TTL(초)입니다. 기본값은 1800, 최대는 21600입니다.
  • baseUrl (string): 뷰어 URL origin override입니다. http 또는 https여야 하며 query/hash는 허용되지 않습니다.
유효성 검사 및 제한:
  • before, after는 각각 최대 512 KiB입니다.
  • patch는 최대 2 MiB입니다.
  • path는 최대 2048바이트입니다.
  • lang는 최대 128바이트입니다.
  • title은 최대 1024바이트입니다.
  • Patch complexity 상한: 최대 128개 파일, 총 120000줄입니다.
  • patchbefore 또는 after를 함께 보내면 거부됩니다.
  • 렌더링 파일 안전 제한(PNG와 PDF 모두 적용):
    • fileQuality: "standard": 최대 8 MP(8,000,000 렌더링 픽셀)
    • fileQuality: "hq": 최대 14 MP(14,000,000 렌더링 픽셀)
    • fileQuality: "print": 최대 24 MP(24,000,000 렌더링 픽셀)
    • PDF는 추가로 최대 50페이지 제한이 있습니다.

출력 details 계약

도구는 구조화된 메타데이터를 details 아래에 반환합니다. 뷰어를 생성하는 모드에서 공통으로 제공되는 필드:
  • artifactId
  • viewerUrl
  • viewerPath
  • title
  • expiresAt
  • inputKind
  • fileCount
  • mode
PNG 또는 PDF가 렌더링될 때 제공되는 파일 필드:
  • filePath
  • path (filePath와 같은 값이며, message 도구와의 호환성을 위해 제공)
  • fileBytes
  • fileFormat
  • fileQuality
  • fileScale
  • fileMaxWidth
모드 동작 요약:
  • mode: "view": 뷰어 필드만 반환합니다.
  • mode: "file": 파일 필드만 반환하며, 뷰어 아티팩트는 없습니다.
  • mode: "both": 뷰어 필드와 파일 필드를 함께 반환합니다. 파일 렌더링이 실패하면 뷰어는 여전히 반환되고 fileError가 포함됩니다.

접힌 변경 없음 구간

  • 뷰어는 N unmodified lines 같은 행을 표시할 수 있습니다.
  • 그 행의 펼치기 컨트롤은 조건부이며, 모든 입력 종류에서 항상 보장되지는 않습니다.
  • 렌더링된 diff에 펼칠 수 있는 컨텍스트 데이터가 있을 때 펼치기 컨트롤이 나타나며, 이는 보통 before/after 입력에서 흔합니다.
  • 많은 unified patch 입력에서는 생략된 컨텍스트 본문이 파싱된 patch hunk에 포함되어 있지 않기 때문에, 펼치기 버튼 없이 해당 행만 표시될 수 있습니다. 이는 예상된 동작입니다.
  • expandUnchanged는 펼칠 수 있는 컨텍스트가 존재할 때만 적용됩니다.

플러그인 기본값

플러그인 전역 기본값은 ~/.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",
          },
        },
      },
    },
  },
}
지원되는 기본값:
  • fontFamily
  • fontSize
  • lineSpacing
  • layout
  • showLineNumbers
  • diffIndicators
  • wordWrap
  • background
  • theme
  • fileFormat
  • fileQuality
  • fileScale
  • fileMaxWidth
  • mode
명시적으로 지정한 도구 매개변수는 이러한 기본값을 덮어씁니다.

보안 설정

  • security.allowRemoteViewer (boolean, 기본값 false)
    • false: 뷰어 라우트에 대한 loopback 이외 요청은 거부됩니다.
    • true: 토큰화된 경로가 유효하면 원격 뷰어 접근이 허용됩니다.
예시:
{
  plugins: {
    entries: {
      diffs: {
        enabled: true,
        config: {
          security: {
            allowRemoteViewer: false,
          },
        },
      },
    },
  },
}

아티팩트 수명 주기와 저장소

  • 아티팩트는 임시 하위 폴더 $TMPDIR/openclaw-diffs 아래에 저장됩니다.
  • 뷰어 아티팩트 메타데이터에는 다음이 포함됩니다:
    • 무작위 아티팩트 ID(20자리 16진수)
    • 무작위 토큰(48자리 16진수)
    • createdAtexpiresAt
    • 저장된 viewer.html 경로
  • 지정하지 않으면 기본 뷰어 TTL은 30분입니다.
  • 허용되는 최대 뷰어 TTL은 6시간입니다.
  • 아티팩트 생성 후 정리는 기회가 있을 때마다 실행됩니다.
  • 만료된 아티팩트는 삭제됩니다.
  • 메타데이터가 없을 때는 24시간보다 오래된 폴더를 제거하는 fallback 정리가 수행됩니다.

뷰어 URL 및 네트워크 동작

뷰어 라우트:
  • /plugins/diffs/view/{artifactId}/{token}
뷰어 에셋:
  • /plugins/diffs/assets/viewer.js
  • /plugins/diffs/assets/viewer-runtime.js
URL 구성 동작:
  • baseUrl이 제공되면 엄격한 유효성 검사를 거친 뒤 사용됩니다.
  • baseUrl이 없으면 뷰어 URL은 기본적으로 loopback 127.0.0.1을 사용합니다.
  • 게이트웨이 바인드 모드가 custom이고 gateway.customBindHost가 설정되어 있으면 해당 호스트가 사용됩니다.
baseUrl 규칙:
  • http:// 또는 https://여야 합니다.
  • Query와 hash는 거부됩니다.
  • origin과 선택적 base path만 허용됩니다.

보안 모델

뷰어 하드닝:
  • 기본적으로 loopback 전용입니다.
  • 엄격한 ID 및 토큰 검증이 적용된 토큰화 뷰어 경로를 사용합니다.
  • 뷰어 응답 CSP:
    • default-src 'none'
    • 스크립트와 에셋은 self에서만 허용
    • 외부로 나가는 connect-src 없음
  • 원격 접근이 활성화되어 있을 때 원격 miss throttling 적용:
    • 60초당 실패 40회
    • 60초 잠금 (429 Too Many Requests)
파일 렌더링 하드닝:
  • 스크린샷 브라우저 요청 라우팅은 기본 거부(deny-by-default)입니다.
  • http://127.0.0.1/plugins/diffs/assets/*의 로컬 뷰어 에셋만 허용됩니다.
  • 외부 네트워크 요청은 차단됩니다.

파일 모드용 브라우저 요구 사항

mode: "file"mode: "both"에는 Chromium 호환 브라우저가 필요합니다. 해결 순서:
  1. OpenClaw config의 browser.executablePath
  2. 환경 변수:
    • OPENCLAW_BROWSER_EXECUTABLE_PATH
    • BROWSER_EXECUTABLE_PATH
    • PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
  3. 플랫폼 명령/경로 탐색 fallback
일반적인 실패 메시지:
  • Diff PNG/PDF rendering requires a Chromium-compatible browser...
Chrome, Chromium, Edge, Brave를 설치하거나 위 실행 파일 경로 옵션 중 하나를 설정해 해결할 수 있습니다.

문제 해결

입력 유효성 검사 오류:
  • Provide patch or both before and after text.
    • beforeafter를 모두 포함하거나 patch를 제공합니다.
  • Provide either patch or before/after input, not both.
    • 입력 모드를 섞지 마십시오.
  • Invalid baseUrl: ...
    • query/hash 없이 선택적 path를 포함한 http(s) origin을 사용합니다.
  • {field} exceeds maximum size (...)
    • payload 크기를 줄이십시오.
  • Large patch rejection
    • patch 파일 수 또는 총 줄 수를 줄이십시오.
뷰어 접근성 문제:
  • 뷰어 URL은 기본적으로 127.0.0.1로 해석됩니다.
  • 원격 접근 시나리오에서는 다음 중 하나를 사용합니다:
    • 각 도구 호출마다 baseUrl을 전달하거나
    • gateway.bind=customgateway.customBindHost를 사용합니다
  • 외부 뷰어 접근이 실제로 필요할 때만 security.allowRemoteViewer를 활성화하십시오.
변경 없음 행에 펼치기 버튼이 없음:
  • patch 입력에서, patch가 펼칠 수 있는 컨텍스트를 담고 있지 않으면 이런 일이 발생할 수 있습니다.
  • 이는 예상된 동작이며 뷰어 실패를 의미하지 않습니다.
아티팩트를 찾을 수 없음:
  • TTL 만료로 인해 아티팩트가 만료되었습니다.
  • 토큰 또는 경로가 변경되었습니다.
  • 정리 작업이 오래된 데이터를 제거했습니다.

운영 가이드

  • 로컬에서 캔버스로 상호작용형 검토를 할 때는 mode: "view"를 우선 사용하십시오.
  • 첨부 파일이 필요한 외부 채팅 채널에는 mode: "file"을 우선 사용하십시오.
  • 배포 환경에서 원격 뷰어 URL이 필요한 경우가 아니면 allowRemoteViewer는 비활성화 상태로 유지하십시오.
  • 민감한 diff에는 명시적으로 짧은 ttlSeconds를 설정하십시오.
  • 꼭 필요하지 않다면 diff 입력에 비밀 정보를 넣지 마십시오.
  • 채널이 이미지를 과하게 압축하는 경우(예: Telegram, WhatsApp)에는 PDF 출력(fileFormat: "pdf")을 우선 사용하십시오.
Diff 렌더링 엔진:

관련 문서