Markdown formatting
OpenClaw는 outbound Markdown을 channel별 output으로 렌더링하기 전에 공유 intermediate representation (IR)으로 변환합니다. IR은 source text를 그대로 유지하면서 style/link span을 따로 들고 있으므로, chunking과 rendering이 channel마다 일관되게 동작할 수 있습니다.Goals
- Consistency: 한 번 parse하고 여러 renderer가 사용
- Safe chunking: inline formatting이 chunk 사이에서 깨지지 않도록 rendering 전에 split
- Channel fit: 같은 IR을 다시 parse하지 않고 Slack mrkdwn, Telegram HTML, Signal style range로 매핑
Pipeline
- Parse Markdown -> IR
- IR은 plain text와 style span(bold/italic/strike/code/spoiler), link span으로 구성됩니다.
- offset은 UTF-16 code unit 기준이므로 Signal style range와 맞습니다.
- table은 해당 channel이 table conversion을 opt-in한 경우에만 parse합니다.
- Chunk IR (format-first)
- chunking은 rendering 전에 IR text 기준으로 일어납니다.
- inline formatting은 chunk 사이에서 split되지 않으며, span은 chunk별로 slice됩니다.
- Render per channel
- Slack: mrkdwn token (bold/italic/strike/code), link는
<url|label> - Telegram: HTML tag (
<b>,<i>,<s>,<code>,<pre><code>,<a href>) - Signal: plain text +
text-stylerange, label이 URL과 다르면label (url)
- Slack: mrkdwn token (bold/italic/strike/code), link는
IR example
Input Markdown:Where it is used
- Slack, Telegram, Signal outbound adapter는 IR에서 렌더링합니다.
- 다른 channel(WhatsApp, iMessage, MS Teams, Discord)은 아직 plain text 또는 자체 formatting rule을 사용하고, table conversion이 켜져 있으면 chunking 전에 table conversion을 적용합니다.
Table handling
Markdown table은 chat client마다 지원이 제각각입니다.markdown.tables로 channel별(그리고 account별) conversion을 제어하세요.
code: table을 code block으로 렌더링 (대부분의 channel 기본값)bullets: row마다 bullet point로 변환 (Signal + WhatsApp 기본값)off: table parse와 conversion 비활성화, raw table text 그대로 전달
Chunking rules
- chunk limit은 channel adapter/config에서 오며 IR text에 적용됩니다.
- code fence는 trailing newline을 포함한 단일 block으로 유지되어 channel이 올바르게 렌더링할 수 있습니다.
- list prefix와 blockquote prefix는 IR text 일부이므로 prefix 중간에서 split되지 않습니다.
- inline style(bold/italic/strike/inline-code/spoiler)는 chunk 사이에서 절대 split되지 않으며, renderer가 chunk 안에서 style을 다시 엽니다.
Link policy
- Slack:
[label](url)-><url|label>; bare URL은 그대로 유지. parse 시 autolink를 꺼서 double-linking을 막습니다. - Telegram:
[label](url)-><a href="url">label</a>(HTML parse mode) - Signal:
[label](url)-> label이 URL과 다를 때만label (url)
Spoilers
spoiler marker(||spoiler||)는 Signal에서만 parse되어 SPOILER style range로 매핑됩니다.
다른 channel은 plain text로 취급합니다.
How to add or update a channel formatter
- Parse once: shared
markdownToIR(...)helper를 channel별 option(autolink, heading style, blockquote prefix)에 맞춰 사용 - Render:
renderMarkdownWithMarkers(...)와 style marker map(또는 Signal style range)으로 renderer 구현 - Chunk: rendering 전에
chunkMarkdownIR(...)호출, 각 chunk를 개별 렌더링 - Wire adapter: channel outbound adapter가 새 chunker와 renderer를 사용하도록 연결
- Test: format test와, chunking을 쓰는 channel이면 outbound delivery test도 갱신
Common gotchas
- Slack angle-bracket token(
<@U123>,<#C123>,<https://...>)은 보존되어야 하며, raw HTML은 안전하게 escape해야 합니다. - Telegram HTML은 tag 바깥 text를 escape하지 않으면 markup이 깨집니다.
- Signal style range는 UTF-16 offset 기준입니다. code point offset을 쓰면 안 됩니다.
- fenced code block은 closing marker가 자기 줄에 오도록 trailing newline을 유지하세요.