Markdown-Formatierung

OpenClaw formatiert ausgehende Markdown-Nachrichten, indem es sie zuerst in eine gemeinsame Zwischenrepräsentation (IR) umwandelt, bevor die channel-spezifische Ausgabe gerendert wird. Die IR behält den Quelltext bei und enthält Style- und Link-Spans, damit Chunking und Rendering über alle Channels hinweg konsistent bleiben.

Ziele

  • Konsistenz: ein Parse-Schritt, mehrere Renderer.
  • Sicheres Chunking: Text wird vor dem Rendering aufgeteilt, damit Inline-Formatierungen nie über Chunk-Grenzen hinweg brechen.
  • Channel-Anpassung: dieselbe IR wird auf Slack mrkdwn, Telegram HTML und Signal Style-Ranges gemappt — ohne Markdown erneut zu parsen.

Pipeline

  1. Markdown -> IR parsen
    • Die IR ist Klartext plus Style-Spans (bold/italic/strike/code/spoiler) und Link-Spans.
    • Offsets sind UTF-16 Code Units, damit Signal Style-Ranges mit der API übereinstimmen.
    • Tabellen werden nur geparst, wenn ein Channel die Tabellenkonvertierung aktiviert hat.
  2. IR chunken (Format-first)
    • Das Chunking erfolgt auf dem IR-Text vor dem Rendering.
    • Inline-Formatierungen werden nicht über Chunks aufgeteilt; Spans werden pro Chunk geschnitten.
  3. Pro Channel rendern
    • Slack: mrkdwn-Tokens (bold/italic/strike/code), Links als <url|label>.
    • Telegram: HTML-Tags (<b>, <i>, <s>, <code>, <pre><code>, <a href>).
    • Signal: Klartext + text-style Ranges; Links werden zu label (url), wenn das Label abweicht.

IR-Beispiel

Eingabe-Markdown:

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

IR (schematisch):

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

Wo es verwendet wird

  • Slack-, Telegram- und Signal-Outbound-Adapter rendern aus der IR.
  • Andere Channels (WhatsApp, iMessage, MS Teams, Discord) verwenden weiterhin Klartext oder ihre eigenen Formatierungsregeln, wobei die Markdown-Tabellenkonvertierung vor dem Chunking angewendet wird, wenn aktiviert.

Tabellenbehandlung

Markdown-Tabellen werden nicht einheitlich von Chat-Clients unterstützt. Verwende markdown.tables, um die Konvertierung pro Channel (und pro Account) zu steuern.

  • code: Tabellen als Code-Blöcke rendern (Standard für die meisten Channels).
  • bullets: Jede Zeile in Aufzählungspunkte umwandeln (Standard für Signal + WhatsApp).
  • off: Tabellen-Parsing und -Konvertierung deaktivieren; roher Tabellentext wird durchgereicht.

Config-Keys:

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

Chunking-Regeln

  • Chunk-Limits kommen von Channel-Adaptern/Config und werden auf den IR-Text angewendet.
  • Code-Fences werden als einzelner Block mit abschließendem Newline erhalten, damit Channels sie korrekt rendern.
  • Listen-Präfixe und Blockquote-Präfixe sind Teil des IR-Texts, sodass das Chunking nicht mitten im Präfix trennt.
  • Inline-Styles (bold/italic/strike/inline-code/spoiler) werden nie über Chunks aufgeteilt; der Renderer öffnet Styles innerhalb jedes Chunks neu.

Mehr zum Chunking-Verhalten über Channels hinweg findest du unter Streaming + Chunking.

  • Slack: [label](url) -> <url|label>; nackte URLs bleiben nackt. Autolink ist beim Parsen deaktiviert, um Doppel-Verlinkung zu vermeiden.
  • Telegram: [label](url) -> <a href="url">label</a> (HTML-Parse-Modus).
  • Signal: [label](url) -> label (url), außer das Label entspricht der URL.

Spoiler

Spoiler-Marker (||spoiler||) werden nur für Signal geparst, wo sie auf SPOILER-Style-Ranges gemappt werden. Andere Channels behandeln sie als Klartext.

Einen Channel-Formatter hinzufügen oder aktualisieren

  1. Einmal parsen: Verwende den gemeinsamen markdownToIR(...)-Helper mit channel-spezifischen Optionen (autolink, heading style, blockquote prefix).
  2. Rendern: Implementiere einen Renderer mit renderMarkdownWithMarkers(...) und einer Style-Marker-Map (oder Signal Style-Ranges).
  3. Chunken: Rufe chunkMarkdownIR(...) vor dem Rendering auf; rendere jeden Chunk.
  4. Adapter verdrahten: Aktualisiere den Channel-Outbound-Adapter, um den neuen Chunker und Renderer zu verwenden.
  5. Testen: Füge Format-Tests hinzu oder aktualisiere sie, sowie einen Outbound-Delivery-Test, wenn der Channel Chunking verwendet.

Häufige Stolperfallen

  • Slack-Angle-Bracket-Tokens (<@U123>, <#C123>, <https://...>) müssen erhalten bleiben; escape rohes HTML sicher.
  • Telegram HTML erfordert das Escapen von Text außerhalb von Tags, um kaputtes Markup zu vermeiden.
  • Signal Style-Ranges hängen von UTF-16-Offsets ab; verwende keine Code-Point-Offsets.
  • Erhalte abschließende Newlines für Fenced-Code-Blöcke, damit schließende Marker auf ihrer eigenen Zeile landen.