Định dạng Markdown

OpenClaw xử lý Markdown đầu ra bằng cách chuyển đổi nó thành một dạng biểu diễn trung gian (IR - intermediate representation) trước khi render output cho từng channel cụ thể. IR giữ nguyên văn bản gốc trong khi mang theo các style/link spans, giúp việc chia nhỏ và render nhất quán trên các channel.

Mục tiêu

  • Nhất quán: parse một lần, render nhiều lần.
  • Chia nhỏ an toàn: chia văn bản trước khi render để định dạng inline không bao giờ bị cắt ngang giữa các chunk.
  • Phù hợp từng channel: ánh xạ cùng một IR sang Slack mrkdwn, Telegram HTML, và Signal style ranges mà không cần parse lại Markdown.

Pipeline

  1. Parse Markdown -> IR
    • IR là văn bản thuần cộng với style spans (bold/italic/strike/code/spoiler) và link spans.
    • Offset tính theo đơn vị UTF-16 code units để Signal style ranges khớp với API của nó.
    • Bảng (tables) chỉ được parse khi channel bật tính năng chuyển đổi bảng.
  2. Chunk IR (format-first)
    • Việc chia nhỏ diễn ra trên văn bản IR trước khi render.
    • Định dạng inline không bị chia cắt giữa các chunk; spans được cắt theo từng chunk.
  3. Render theo từng channel
    • Slack: mrkdwn tokens (bold/italic/strike/code), links dạng <url|label>.
    • Telegram: HTML tags (<b>, <i>, <s>, <code>, <pre><code>, <a href>).
    • Signal: văn bản thuần + text-style ranges; links trở thành label (url) khi label khác URL.

Ví dụ về IR

Input Markdown:

Hello **world** — see [docs](https://docs.openclaw.ai).

IR (dạng sơ đồ):

{
  "text": "Hello world — see docs.",
  "styles": [{ "start": 6, "end": 11, "style": "bold" }],
  "links": [{ "start": 19, "end": 23, "href": "https://docs.openclaw.ai" }]
}

Nơi sử dụng

  • Các adapter đầu ra của Slack, Telegram, và Signal render từ IR.
  • Các channel khác (WhatsApp, iMessage, MS Teams, Discord) vẫn dùng văn bản thuần hoặc quy tắc định dạng riêng của chúng, với việc chuyển đổi bảng Markdown được áp dụng trước khi chia nhỏ nếu được bật.

Xử lý bảng (tables)

Bảng Markdown không được hỗ trợ nhất quán trên các ứng dụng chat. Dùng markdown.tables để kiểm soát việc chuyển đổi theo từng channel (và từng account).

  • code: render bảng dưới dạng code blocks (mặc định cho hầu hết các channel).
  • bullets: chuyển mỗi hàng thành bullet points (mặc định cho Signal + WhatsApp).
  • off: tắt parse và chuyển đổi bảng; văn bản bảng thô được giữ nguyên.

Config keys:

channels:
  discord:
    markdown:
      tables: code
    accounts:
      work:
        markdown:
          tables: off

Quy tắc chia nhỏ (chunking)

  • Giới hạn chunk đến từ adapter/config của channel và được áp dụng lên văn bản IR.
  • Code fences được giữ nguyên như một khối duy nhất với newline ở cuối để các channel render đúng.
  • List prefixes và blockquote prefixes là một phần của văn bản IR, nên việc chia nhỏ không cắt giữa prefix.
  • Inline styles (bold/italic/strike/inline-code/spoiler) không bao giờ bị chia cắt giữa các chunk; renderer mở lại styles bên trong mỗi chunk.

Nếu các bạn cần thêm thông tin về hành vi chunking trên các channel, xem Streaming + chunking.

  • Slack: [label](url) -> <url|label>; bare URLs giữ nguyên. Autolink bị tắt trong quá trình parse để tránh double-linking.
  • Telegram: [label](url) -> <a href="url">label</a> (HTML parse mode).
  • Signal: [label](url) -> label (url) trừ khi label trùng với URL.

Spoilers

Spoiler markers (||spoiler||) chỉ được parse cho Signal, nơi chúng ánh xạ sang SPOILER style ranges. Các channel khác xử lý chúng như văn bản thuần.

Cách thêm hoặc cập nhật channel formatter

  1. Parse một lần: dùng helper markdownToIR(...) chung với các tùy chọn phù hợp cho channel (autolink, heading style, blockquote prefix).
  2. Render: implement một renderer với renderMarkdownWithMarkers(...) và một style marker map (hoặc Signal style ranges).
  3. Chunk: gọi chunkMarkdownIR(...) trước khi render; render từng chunk.
  4. Wire adapter: cập nhật channel outbound adapter để dùng chunker và renderer mới.
  5. Test: thêm hoặc cập nhật format tests và outbound delivery test nếu channel dùng chunking.

Những lỗi thường gặp

  • Slack angle-bracket tokens (<@U123>, <#C123>, <https://...>) phải được giữ nguyên; escape raw HTML một cách an toàn.
  • Telegram HTML yêu cầu escape văn bản bên ngoài tags để tránh markup bị hỏng.
  • Signal style ranges phụ thuộc vào UTF-16 offsets; không dùng code point offsets.
  • Giữ trailing newlines cho fenced code blocks để closing markers nằm trên dòng riêng của chúng.