Diffs

diffs는 간단한 기본 제공 시스템 가이드와 함께 변경 내용을 에이전트용 읽기 전용 diff 아티팩트로 변환하는 컴패니언 스킬을 갖춘 선택적 플러그인 도구입니다.

다음 중 하나를 입력으로 받습니다:

  • beforeafter 텍스트
  • 통합 patch

반환 가능한 출력:

  • 캔버스 프레젠테이션을 위한 게이트웨이 뷰어 URL
  • 메시지 전달을 위한 렌더링된 파일 경로 (PNG 또는 PDF)
  • 한 번의 호출로 두 출력 모두

활성화되면, 플러그인은 시스템 프롬프트에 간결한 사용 가이드를 삽입하고, 에이전트가 더 자세한 지시가 필요한 경우를 위한 상세 스킬도 노출합니다.

빠른 시작

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

플러그인 활성화

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

기본 제공 시스템 가이드 비활성화

diffs 도구는 활성화하되 기본 제공 시스템 프롬프트 가이드를 비활성화하려면, plugins.entries.diffs.hooks.allowPromptInjectionfalse로 설정하세요:

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

이렇게 하면 플러그인, 도구 및 컴패니언 스킬은 유지하면서 diffs 플러그인의 before_prompt_build 후크를 차단합니다.

가이드와 도구를 모두 비활성화하려면 대신 플러그인을 비활성화하세요.

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

  1. 에이전트가 diffs를 호출합니다.
  2. 에이전트가 details 필드를 읽습니다.
  3. 에이전트가 다음 중 하나를 수행합니다:
    • details.viewerUrlcanvas present로 열기
    • details.filePathmessagepath 또는 filePath를 사용하여 전송
    • 두 가지 모두 수행

입력 예시

Before와 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): 통합 diff 텍스트. before/after와 상호 배타적.
  • path (string): before/after 모드의 표시 파일명.
  • lang (string): before/after 모드의 언어 오버라이드 힌트.
  • title (string): 뷰어 제목 오버라이드.
  • 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): 장치 스케일 오버라이드 (1-4).
  • fileMaxWidth (number): CSS 픽셀 기준 최대 렌더 너비 (640-2400).
  • ttlSeconds (number): 뷰어 아티팩트 TTL(초). 기본값 1800, 최대 21600.
  • baseUrl (string): 뷰어 URL 출처 오버라이드. http 또는 https여야 하며, 쿼리/해시 불가.

유효성 검사 및 제한:

  • beforeafter는 각각 최대 512 KiB.
  • patch는 최대 2 MiB.
  • path는 최대 2048 바이트.
  • lang은 최대 128 바이트.
  • title은 최대 1024 바이트.
  • 패치 복잡성 상한: 최대 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 입력에서 일반적입니다.
  • 많은 통합 패치 입력에서 생략된 컨텍스트 본문은 파싱된 패치 헝크에서 사용할 수 없으므로, 확장 컨트롤 없이 행이 나타날 수 있습니다. 이는 예상된 동작입니다.
  • 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: 비루프백 요청이 뷰어 라우트에서 거부됩니다.
    • true: 토큰화된 경로가 유효하면 원격 뷰어가 허용됩니다.

예시:

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

아티팩트 생명주기 및 저장소

  • 아티팩트는 임시 하위 폴더에 저장됩니다: $TMPDIR/openclaw-diffs.
  • 뷰어 아티팩트 메타데이터에 포함되는 것:
    • 랜덤 아티팩트 ID (20 hex 문자)
    • 랜덤 토큰 (48 hex 문자)
    • createdAtexpiresAt
    • 저장된 viewer.html 경로
  • 미지정 시 기본 뷰어 TTL은 30분.
  • 최대 허용 뷰어 TTL은 6시간.
  • 정리는 아티팩트 생성 후 기회적으로 실행.
  • 만료된 아티팩트는 삭제됩니다.
  • 메타데이터가 없는 경우 24시간 이상 된 오래된 폴더를 폴백 정리합니다.

뷰어 URL 및 네트워크 동작

뷰어 라우트:

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

뷰어 에셋:

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

URL 구성 동작:

  • baseUrl이 제공되면, 엄격한 검증 후 사용됩니다.
  • baseUrl 없이, 뷰어 URL은 기본적으로 루프백 127.0.0.1로 설정.
  • 게이트웨이 바인드 모드가 custom이고 gateway.customBindHost가 설정된 경우, 해당 호스트가 사용됩니다.

baseUrl 규칙:

  • http:// 또는 https://여야 합니다.
  • 쿼리와 해시는 거부됩니다.
  • 출처와 선택적 기본 경로가 허용됩니다.

보안 모델

뷰어 보안 강화:

  • 기본적으로 루프백 전용.
  • 엄격한 ID 및 토큰 검증을 갖춘 토큰화된 뷰어 경로.
  • 뷰어 응답 CSP:
    • default-src 'none'
    • self에서만 스크립트 및 에셋
    • 아웃바운드 connect-src 없음
  • 원격 접근 활성화 시 원격 미스 스로틀링:
    • 60초당 40번 실패
    • 60초 잠금 (429 Too Many Requests)

파일 렌더링 보안 강화:

  • 스크린샷 브라우저 요청 라우팅은 기본 거부.
  • http://127.0.0.1/plugins/diffs/assets/*의 로컬 뷰어 에셋만 허용.
  • 외부 네트워크 요청은 차단됩니다.

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

mode: "file"mode: "both"에는 Chromium 호환 브라우저가 필요합니다.

탐색 순서:

  1. OpenClaw 설정의 browser.executablePath.
  2. 환경 변수:
    • OPENCLAW_BROWSER_EXECUTABLE_PATH
    • BROWSER_EXECUTABLE_PATH
    • PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
  3. 플랫폼 명령/경로 탐색 폴백.

일반적인 오류 텍스트:

  • 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: ...
    • 선택적 경로가 있는 http(s) 출처를 사용하되, 쿼리/해시는 불가.
  • {field} exceeds maximum size (...)
    • 페이로드 크기를 줄이세요.
  • 대규모 패치 거부
    • 패치 파일 수 또는 총 줄 수를 줄이세요.

뷰어 접근성 문제:

  • 뷰어 URL은 기본적으로 127.0.0.1로 해석됩니다.
  • 원격 접근 시나리오의 경우:
    • 도구 호출마다 baseUrl을 전달하거나,
    • gateway.bind=customgateway.customBindHost를 사용
  • 외부 뷰어 접근을 의도하는 경우에만 security.allowRemoteViewer를 활성화하세요.

변경되지 않은 줄 행에 확장 버튼이 없는 경우:

  • 패치 입력이 확장 가능한 컨텍스트를 포함하지 않는 경우 발생할 수 있습니다.
  • 이는 예상된 동작이며 뷰어 오류를 의미하지 않습니다.

아티팩트를 찾을 수 없는 경우:

  • TTL에 의해 아티팩트가 만료됨.
  • 토큰 또는 경로가 변경됨.
  • 정리가 오래된 데이터를 제거함.

운영 가이드

  • 캔버스에서의 로컬 대화형 검토에는 mode: "view"를 선호하세요.
  • 첨부 파일이 필요한 아웃바운드 채팅 채널에는 mode: "file"을 선호하세요.
  • 배포에 원격 뷰어 URL이 필요한 경우가 아니면 allowRemoteViewer를 비활성화 상태로 유지하세요.
  • 민감한 diff에는 명시적으로 짧은 ttlSeconds를 설정하세요.
  • 필요하지 않은 경우 diff 입력에 시크릿을 보내지 마세요.
  • 채널이 이미지를 과도하게 압축하는 경우(예: Telegram 또는 WhatsApp), PDF 출력(fileFormat: "pdf")을 선호하세요.

Diff 렌더링 엔진:

  • Diffs에 의해 구동됩니다.

관련 문서