Concept internals
Markdown 서식
OpenClaw은 발신 Markdown을 채널별 출력으로 렌더링하기 전에 공유 중간 표현 (IR)으로 변환합니다. IR은 일반 텍스트와 스타일/링크 범위를 유지하므로 한 번의 파싱 단계로 모든 채널을 처리할 수 있으며, 청크 분할 시 범위 중간에서 서식이 나뉘지 않습니다.
파이프라인
- Markdown을 IR로 파싱 (
markdownToIR) - 일반 텍스트와 스타일 범위 (굵게, 기울임꼴, 취소선, 코드, 코드 블록, 스포일러, 인용문, 제목 1~6) 및 링크 범위입니다. 오프셋은 UTF-16 코드 단위이므로 Signal 스타일 범위가 해당 API와 직접 정렬됩니다. 표는 채널이 표 모드를 사용하도록 설정된 경우에만 파싱됩니다. - IR 청크 분할 (
chunkMarkdownIR/renderMarkdownIRChunksWithinLimit)- 렌더링 전에 IR 텍스트를 분할하므로 인라인 스타일과 링크가 경계를 가로질러 끊어지는 대신 청크별로 분할됩니다.
- 채널별 렌더링 (
renderMarkdownWithMarkers) - 스타일 마커 맵이 범위를 채널의 네이티브 마크업으로 변환합니다.
| 채널 | 렌더러 | 참고 |
|---|---|---|
| Slack | mrkdwn 토큰 (*bold*, _italic_, `code`, 코드 펜스) |
링크는 <url|label>이 되며, 중복 링크를 방지하기 위해 파싱 중 자동 링크를 비활성화합니다 |
| Telegram | HTML 태그 (<b>, <i>, <s>, <code>, <pre><code>, <a href>, <tg-spoiler>) |
richMessages가 켜져 있으면 리치 메시지 표와 제목(<h1>-<h6>)도 지원합니다 |
| Signal | 일반 텍스트 + text-style 범위 |
레이블이 URL과 다르면 링크를 label (url)로 렌더링합니다 |
| Discord, WhatsApp, iMessage, Microsoft Teams 및 기타 채널 | 일반 텍스트 | IR 기반 스타일링은 없으며, Markdown 표 변환은 여전히 convertMarkdownTables를 통해 실행됩니다 |
IR 예시
입력 Markdown: OC_I18N_900000 IR(개략도): OC_I18N_900001
표 처리
markdown.tables는 채널별로, 선택적으로 계정별로 채널이 Markdown 표를
변환하는 방식을 제어합니다.
| 모드 | 동작 |
|---|---|
code |
코드 블록 안에 정렬된 ASCII 표로 렌더링합니다(대체 기본값) |
bullets |
각 행을 label: value 글머리 기호 항목으로 변환합니다 |
block |
전송 수단에서 지원하는 경우 네이티브 표를 유지하고, 그렇지 않으면 code로 대체합니다 |
off |
표 파싱을 비활성화하며, 원시 표 텍스트를 변경 없이 전달합니다 |
채널별 Plugin 기본값: Signal, WhatsApp, Matrix의 기본값은
bullets이고, Mattermost의 기본값은 off이며, Telegram의 기본값은 block입니다
(계정에서 richMessages가 활성화되어 있지 않으면 code로 해석됨). 명시적인
Plugin 기본값이 없는 채널은 code를 대체 기본값으로 사용합니다.
OC_I18N_900002
청크 분할 규칙
- 청크 제한은 채널 어댑터/구성에서 가져오며 렌더링된 출력이 아니라 IR 텍스트에 적용됩니다.
- 펜스로 둘러싸인 코드 블록은 채널이 닫는 펜스를 올바르게 렌더링할 수 있도록 후행 줄 바꿈이 포함된 하나의 블록으로 유지됩니다.
- 목록 및 인용문 접두사는 IR 텍스트의 일부이므로 청크 분할 시 접두사 중간에서 나뉘지 않습니다.
- 인라인 스타일은 청크 사이에서 절대 분할되지 않으며, 렌더러는 다음 청크의 시작 부분에서 열린 스타일을 다시 엽니다.
채널 간 청크 경계 및 전달 동작은 스트리밍 및 청크 분할을 참조하세요.
링크 정책
- Slack:
[label](url)-><url|label>이며, 단독 URL은 그대로 유지됩니다. - Telegram:
[label](url)-><a href="url">label</a>입니다(HTML 파싱 모드). - Signal: 레이블이 이미 URL과 일치하지 않는 한
[label](url)->label (url)입니다.
스포일러
스포일러 마커(||spoiler||)는 Signal(SPOILER 스타일 범위에 매핑)과
Telegram(<tg-spoiler>에 매핑)에서 파싱됩니다. 다른 채널은
||...||을 일반 텍스트로 처리합니다.
채널 포매터 추가 또는 업데이트
- 채널에 적합한 옵션(
autolink,headingStyle,blockquotePrefix,tableMode)을 전달하여markdownToIR(...)로 한 번만 파싱합니다. - 스타일 마커 맵과 함께
renderMarkdownWithMarkers(...)로 렌더링합니다 (또는 Signal과 같은 전송 수단에는 사용자 지정 스타일 범위 로직을 사용합니다). - 각 청크를 렌더링하기 전에
chunkMarkdownIR(...)또는renderMarkdownIRChunksWithinLimit(...)로 청크를 분할합니다. - 발신 전송 경로에서 새 청크 분할기와 렌더러를 호출하도록 어댑터를 연결합니다.
- 채널에서 청크를 분할하는 경우 형식 테스트와 발신 전달 테스트로 테스트합니다.
흔히 발생하는 문제
- Slack 꺾쇠괄호 토큰(
<@U123>,<#C123>,<https://...>)은 이스케이프 처리 후에도 유지되어야 하며, 원시 HTML은 여전히 안전하게 이스케이프해야 합니다. - Telegram HTML에서는 마크업이 손상되지 않도록 태그 외부의 텍스트를 이스케이프해야 합니다.
- Signal 스타일 범위는 코드 포인트 오프셋이 아닌 UTF-16 오프셋을 사용합니다.
- 닫는 마커가 독립된 줄에 위치하도록 펜스로 둘러싸인 코드 블록의 후행 줄 바꿈을 유지하세요.
관련 항목
Was this useful?