Formato Markdown

OpenClaw da formato al Markdown saliente convirtiéndolo en una representación intermedia compartida (IR) antes de renderizar la salida específica de cada canal. La IR mantiene el texto fuente intacto mientras lleva tramos de estilo/enlace para que el chunking y el renderizado se mantengan consistentes entre canales.

Objetivos

  • Consistencia: un solo paso de análisis, múltiples renderizadores.
  • Chunking seguro: dividir el texto antes de renderizar para que el formato inline nunca se rompa entre chunks.
  • Adaptación al canal: mapear la misma IR a mrkdwn de Slack, HTML de Telegram y rangos de estilo de Signal sin volver a parsear el Markdown.

Pipeline

  1. Parsear Markdown -> IR
    • La IR es texto plano más tramos de estilo (negrita/cursiva/tachado/código/spoiler) y tramos de enlace.
    • Los offsets son unidades de código UTF-16 para que los rangos de estilo de Signal se alineen con su API.
    • Las tablas solo se parsean cuando un canal opta por la conversión de tablas.
  2. Fragmentar IR (formato primero)
    • El chunking ocurre sobre el texto de la IR antes del renderizado.
    • El formato inline no se divide entre chunks; los tramos se cortan por chunk.
  3. Renderizar por canal
    • Slack: tokens mrkdwn (negrita/cursiva/tachado/código), enlaces como <url|label>.
    • Telegram: etiquetas HTML (<b>, <i>, <s>, <code>, <pre><code>, <a href>).
    • Signal: texto plano + rangos text-style; los enlaces se convierten en label (url) cuando la etiqueta difiere.

Ejemplo de IR

Markdown de entrada:

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

IR (esquemática):

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

Dónde se usa

  • Los adaptadores de salida de Slack, Telegram y Signal renderizan desde la IR.
  • Otros canales (WhatsApp, iMessage, MS Teams, Discord) aún usan texto plano o sus propias reglas de formato, con la conversión de tablas Markdown aplicada antes del chunking cuando está habilitada.

Manejo de tablas

Las tablas Markdown no se soportan de forma consistente en todos los clientes de chat. Usa markdown.tables para controlar la conversión por canal (y por cuenta).

  • code: renderizar tablas como bloques de código (por defecto para la mayoría de los canales).
  • bullets: convertir cada fila en viñetas (por defecto para Signal + WhatsApp).
  • off: desactivar el análisis y conversión de tablas; el texto de tabla crudo pasa tal cual.

Claves de configuración:

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

Reglas de chunking

  • Los límites de chunk provienen de los adaptadores/configuración del canal y se aplican al texto de la IR.
  • Los bloques de código delimitados se preservan como un bloque único con un salto de línea final para que los canales los rendericen correctamente.
  • Los prefijos de lista y prefijos de cita son parte del texto de la IR, por lo que el chunking no corta a mitad de prefijo.
  • Los estilos inline (negrita/cursiva/tachado/código-inline/spoiler) nunca se dividen entre chunks; el renderizador reabre los estilos dentro de cada chunk.

Si necesitas más información sobre el comportamiento de chunking entre canales, consulta Streaming + chunking.

Política de enlaces

  • Slack: [label](/docs/concepts/url) -> <url|label>; las URLs desnudas permanecen desnudas. El autolink se desactiva durante el análisis para evitar doble enlazamiento.
  • Telegram: [label](/docs/concepts/url) -> <a href="url">label</a> (modo de análisis HTML).
  • Signal: [label](/docs/concepts/url) -> label (url) a menos que la etiqueta coincida con la URL.

Spoilers

Los marcadores de spoiler (||spoiler||) solo se parsean para Signal, donde se mapean a rangos de estilo SPOILER. Otros canales los tratan como texto plano.

Cómo agregar o actualizar un formateador de canal

  1. Parsear una vez: usa el helper compartido markdownToIR(...) con opciones apropiadas para el canal (autolink, estilo de encabezado, prefijo de cita).
  2. Renderizar: implementa un renderizador con renderMarkdownWithMarkers(...) y un mapa de marcadores de estilo (o rangos de estilo de Signal).
  3. Fragmentar: llama a chunkMarkdownIR(...) antes de renderizar; renderiza cada chunk.
  4. Conectar adaptador: actualiza el adaptador de salida del canal para usar el nuevo chunker y renderizador.
  5. Probar: agrega o actualiza las pruebas de formato y una prueba de entrega saliente si el canal usa chunking.

Errores comunes

  • Los tokens de corchetes angulares de Slack (<@U123>, <#C123>, <https://...>) deben preservarse; escapa el HTML crudo de forma segura.
  • El HTML de Telegram requiere escapar el texto fuera de las etiquetas para evitar marcado roto.
  • Los rangos de estilo de Signal dependen de offsets UTF-16; no uses offsets de puntos de código.
  • Preserva los saltos de línea finales de los bloques de código delimitados para que los marcadores de cierre queden en su propia línea.