Markdown-Formatierung

OpenClaw formatiert ausgehendes Markdown, indem es in eine gemeinsame Zwischendarstellung (IR) konvertiert wird, bevor die kanalspezifische Ausgabe gerendert wird. Die IR behaelt den Quelltext intakt und traegt Style-/Link-Spans, damit Chunking und Rendering kanaluebergreifend konsistent bleiben.

Ziele

  • Konsistenz: ein Parse-Schritt, mehrere Renderer.
  • Sicheres Chunking: Text vor dem Rendering aufteilen, damit Inline-Formatierung nie ueber Chunk-Grenzen bricht.
  • Kanalanpassung: dieselbe IR auf Slack-mrkdwn, Telegram-HTML und Signal-Style-Ranges abbilden, ohne Markdown erneut zu parsen.

Pipeline

  1. Markdown -> IR parsen
    • Die IR ist Klartext plus Style-Spans (fett/kursiv/durchgestrichen/Code/Spoiler) und Link-Spans.
    • Offsets sind UTF-16-Code-Units, damit Signal-Style-Ranges mit seiner API uebereinstimmen.
    • Tabellen werden nur geparst, wenn ein Kanal die Tabellenkonvertierung aktiviert hat.
  2. IR chunken (Format zuerst)
    • Chunking geschieht am IR-Text vor dem Rendering.
    • Inline-Formatierung wird nicht ueber Chunks aufgeteilt; Spans werden pro Chunk geschnitten.
  3. Pro Kanal rendern
    • Slack: mrkdwn-Tokens (fett/kursiv/durchgestrichen/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 sich Label und URL unterscheiden.

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" }]
}

Einsatzorte

  • Die ausgehenden Adapter fuer Slack, Telegram und Signal rendern aus der IR.
  • Andere Kanaele (WhatsApp, iMessage, MS Teams, Discord) verwenden weiterhin Klartext oder eigene Formatierungsregeln, wobei Markdown-Tabellenkonvertierung vor dem Chunking angewandt wird, wenn aktiviert.

Tabellenbehandlung

Markdown-Tabellen werden nicht einheitlich von allen Chat-Clients unterstuetzt. Verwende markdown.tables, um die Konvertierung pro Kanal (und pro Account) zu steuern.

  • code: Tabellen als Code-Bloecke rendern (Standard fuer die meisten Kanaele).
  • bullets: jede Zeile in Aufzaehlungspunkte umwandeln (Standard fuer Signal und WhatsApp).
  • off: Tabellen-Parsing und -Konvertierung deaktivieren; der rohe Tabellentext wird durchgereicht.

Konfigurations-Schluessel:

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

Chunking-Regeln

  • Chunk-Limits kommen von den Kanal-Adaptern/der Konfiguration und werden auf den IR-Text angewandt.
  • Code-Fences werden als einzelner Block mit abschliessendem Newline beibehalten, damit Kanaele sie korrekt rendern.
  • Listen-Praefixe und Blockquote-Praefixe sind Teil des IR-Textes, sodass Chunking nicht mitten im Praefix trennt.
  • Inline-Styles (fett/kursiv/durchgestrichen/Inline-Code/Spoiler) werden nie ueber Chunks aufgeteilt; der Renderer oeffnet Styles innerhalb jedes Chunks neu.

Wenn du mehr ueber das Chunking-Verhalten kanaluebergreifend wissen moechtest, siehe Streaming und Chunking.

  • Slack: [label](/docs/concepts/url) -> <url|label>; nackte URLs bleiben nackt. Autolink wird beim Parsen deaktiviert, um Doppel-Verlinkung zu vermeiden.
  • Telegram: [label](/docs/concepts/url) -> <a href="url">label</a> (HTML-Parse-Modus).
  • Signal: [label](/docs/concepts/url) -> label (url), es sei denn, Label und URL stimmen ueberein.

Spoiler

Spoiler-Marker (||spoiler||) werden nur fuer Signal geparst, wo sie auf SPOILER-Style-Ranges abgebildet werden. Andere Kanaele behandeln sie als Klartext.

Einen Kanal-Formatter hinzufuegen oder aktualisieren

  1. Einmal parsen: Verwende den gemeinsamen markdownToIR(...)-Helper mit kanalgerechten Optionen (Autolink, Heading-Style, Blockquote-Praefix).
  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 ausgehenden Kanal-Adapter fuer den neuen Chunker und Renderer.
  5. Testen: Fuege Format-Tests und einen ausgehenden Zustellungs-Test hinzu oder aktualisiere sie, wenn der Kanal Chunking nutzt.

Haeufige Stolperfallen

  • Slack-Winkelklammer-Tokens (<@U123>, <#C123>, <https://...>) muessen erhalten bleiben; rohes HTML sicher escapen.
  • Telegram-HTML erfordert Escaping von Text ausserhalb von Tags, um kaputtes Markup zu vermeiden.
  • Signal-Style-Ranges haengen von UTF-16-Offsets ab; verwende keine Code-Point-Offsets.
  • Bewahre abschliessende Newlines fuer Fenced-Code-Bloecke, damit Schlussmarker auf einer eigenen Zeile landen.