> ## Documentation Index
> Fetch the complete documentation index at: https://openclaw.veiseule.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Markdown 서식

# Markdown 서식

OpenClaw 는 아웃바운드 Markdown 을 채널별 출력으로 렌더링하기 전에 공통의 중간 표현(IR)으로 변환하여 서식을 처리합니다. IR 은 소스 텍스트를 그대로 유지하면서 스타일/링크 스팬을 포함하므로, 청킹과 렌더링을 채널 전반에서 일관되게 유지할 수 있습니다.

## 목표

* **일관성:** 한 번의 파싱 단계, 여러 렌더러.
* **안전한 청킹:** 인라인 서식이 청크 간에 끊어지지 않도록 렌더링 전에 텍스트를 분할합니다.
* **채널 적합성:** Markdown 을 다시 파싱하지 않고 동일한 IR 을 Slack mrkdwn, Telegram HTML, Signal 스타일 범위로 매핑합니다.

## 파이프라인

1. **Markdown 파싱 -> IR**
   * IR 은 일반 텍스트와 스타일 스팬(굵게/기울임/취소선/코드/스포일러), 그리고 링크 스팬으로 구성됩니다.
   * 오프셋은 UTF-16 코드 유닛을 사용하여 Signal 스타일 범위가 해당 API 와 정렬되도록 합니다.
   * 테이블은 채널이 테이블 변환을 선택한 경우에만 파싱됩니다.
2. **IR 청킹(서식 우선)**
   * 청킹은 렌더링 전에 IR 텍스트 기준으로 수행됩니다.
   * 인라인 서식은 청크 간에 분리되지 않으며, 스팬은 청크별로 잘립니다.
3. **채널별 렌더링**
   * **Slack:** mrkdwn 토큰(굵게/기울임/취소선/코드), 링크는 `<url|label>`.
   * **Telegram:** HTML 태그(`<b>`, `<i>`, `<s>`, `<code>`, `<pre><code>`, `<a href>`).
   * **Signal:** 일반 텍스트 + `text-style` 범위; 레이블이 다를 경우 링크는 `label (url)` 로 변환됩니다.

## IR 예시

입력 Markdown:

```markdown theme={"theme":{"light":"min-light","dark":"min-dark"}}
Hello **world** — see [docs](https://docs.openclaw.ai).
```

IR(개념도):

```json theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  "text": "Hello world — see docs.",
  "styles": [{ "start": 6, "end": 11, "style": "bold" }],
  "links": [{ "start": 19, "end": 23, "href": "https://docs.openclaw.ai" }]
}
```

## 사용 위치

* Slack, Telegram, Signal 아웃바운드 어댑터는 IR 로부터 렌더링합니다.
* 다른 채널(WhatsApp, iMessage, MS Teams, Discord)은 여전히 일반 텍스트 또는 자체 서식 규칙을 사용하며, 활성화된 경우 Markdown 테이블 변환을 청킹 전에 적용합니다.

## 테이블 처리

Markdown 테이블은 채팅 클라이언트 전반에서 일관되게 지원되지 않습니다. 채널별(및 계정별) 변환을 제어하려면 `markdown.tables` 를 사용하십시오.

* `code`: 테이블을 코드 블록으로 렌더링합니다(대부분의 채널에서 기본값).
* `bullets`: 각 행을 글머리 기호 목록으로 변환합니다(Signal + WhatsApp 의 기본값).
* `off`: 테이블 파싱 및 변환을 비활성화하고 원본 테이블 텍스트를 그대로 전달합니다.

구성 키:

```yaml theme={"theme":{"light":"min-light","dark":"min-dark"}}
channels:
  discord:
    markdown:
      tables: code
    accounts:
      work:
        markdown:
          tables: off
```

## 청킹 규칙

* 청크 제한은 채널 어댑터/설정에서 가져오며 IR 텍스트에 적용됩니다.
* 코드 펜스는 단일 블록으로 보존되며, 채널에서 올바르게 렌더링되도록 끝에 개행을 포함합니다.
* 목록 접두사와 인용문 접두사는 IR 텍스트의 일부이므로, 청킹 시 접두사 중간에서 분리되지 않습니다.
* 인라인 스타일(굵게/기울임/취소선/인라인 코드/스포일러)은 청크 간에 절대 분리되지 않으며, 렌더러는 각 청크 내부에서 스타일을 다시 엽니다.

채널 전반의 청킹 동작에 대한 자세한 내용은
[Streaming + chunking](/concepts/streaming)을 참고하십시오.

## 링크 정책

* **Slack:** `[label](url)` -> `<url|label>`; 순수 URL 은 그대로 유지됩니다. 이중 링크를 방지하기 위해 파싱 중 자동 링크는 비활성화됩니다.
* **Telegram:** `[label](url)` -> `<a href="url">label</a>`(HTML 파싱 모드).
* **Signal:** 레이블이 URL 과 일치하지 않는 경우 `[label](url)` -> `label (url)`.

## 스포일러

스포일러 마커(`||spoiler||`)는 Signal 에서만 파싱되며, SPOILER 스타일 범위로 매핑됩니다. 다른 채널에서는 일반 텍스트로 처리됩니다.

## 채널 포매터 추가 또는 업데이트 방법

1. **한 번만 파싱:** 채널에 적합한 옵션(자동 링크, 제목 스타일, 인용문 접두사)으로 공통 `markdownToIR(...)` 헬퍼를 사용합니다.
2. **렌더링:** `renderMarkdownWithMarkers(...)` 와 스타일 마커 맵(또는 Signal 스타일 범위)을 사용하여 렌더러를 구현합니다.
3. **청킹:** 렌더링 전에 `chunkMarkdownIR(...)` 를 호출하고, 각 청크를 렌더링합니다.
4. **어댑터 연결:** 새로운 청커와 렌더러를 사용하도록 채널 아웃바운드 어댑터를 업데이트합니다.
5. **테스트:** 서식 테스트를 추가 또는 업데이트하고, 채널에서 청킹을 사용하는 경우 아웃바운드 전달 테스트를 추가합니다.

## 자주 발생하는 함정

* Slack 각도 괄호 토큰(`<@U123>`, `<#C123>`, `<https://...>`)은 반드시 보존해야 하며, 원시 HTML 은 안전하게 이스케이프해야 합니다.
* Telegram HTML 은 태그 외부의 텍스트를 이스케이프하지 않으면 마크업이 깨질 수 있습니다.
* Signal 스타일 범위는 UTF-16 오프셋에 의존하므로 코드 포인트 오프셋을 사용하지 마십시오.
* 펜스 코드 블록의 닫는 마커가 자체 줄에 위치하도록 끝 개행을 보존하십시오.
