Markdown formatting(Markdown フォーマット)

OpenClaw は、チャネル固有の出力をレンダリングする前に、送信 Markdown を共有中間表現(IR)に変換してフォーマットします。IR は、ソーステキストをそのまま保持しながら、スタイル/リンクスパンを運ぶため、チャンキング(chunking)とレンダリングはチャネル間で一貫性を保つことができます。

目標

  • 一貫性: 1 つの解析ステップ、複数のレンダラー。
  • 安全なチャンキング: レンダリング前にテキストを分割して、インラインフォーマットがチャンク間で壊れないようにします。
  • チャネルフィット: Markdown を再解析せずに、同じ IR を Slack mrkdwn、Telegram HTML、および Signal スタイル範囲にマッピングします。

パイプライン

  1. Markdown -> IR を解析
    • IR はプレーンテキストに加えて、スタイルスパン(bold/italic/strike/code/spoiler)とリンクスパンです。
    • オフセット(offset)は UTF-16 コード単位であるため、Signal スタイル範囲はその API と整合します。
    • テーブル(table)は、チャネルがテーブル変換をオプトインした場合にのみ解析されます。
  2. IR をチャンク(format-first)
    • チャンキングは、レンダリング前に IR テキストで行われます。
    • インラインフォーマットはチャンク間で分割されません。スパンはチャンクごとにスライスされます。
  3. チャネルごとにレンダリング
    • Slack: mrkdwn トークン(bold/italic/strike/code)、リンクは <url|label> として。
    • Telegram: HTML タグ(<b><i><s><code><pre><code><a href>)。
    • Signal: プレーンテキスト + text-style 範囲。リンクは、ラベルが異なる場合に label (url) になります。

IR の例

入力 Markdown:

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

IR(概要):

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

使用される場所

  • Slack、Telegram、および Signal 送信アダプターは IR からレンダリングします。
  • 他のチャネル(WhatsApp、iMessage、MS Teams、Discord)は、依然としてプレーンテキストまたは独自のフォーマットルールを使用し、有効な場合はチャンキング前に Markdown テーブル変換が適用されます。

テーブル処理

Markdown テーブルは、チャットクライアント間で一貫してサポートされていません。チャネル(およびアカウント)ごとに変換を制御するには、markdown.tables を使用します。

  • code:テーブルをコードブロックとしてレンダリング(ほとんどのチャネルのデフォルト)。
  • bullets:各行を箇条書きに変換(Signal + WhatsApp のデフォルト)。
  • off:テーブルの解析と変換を無効化。生のテーブルテキストが通過します。

設定キー:

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

チャンキングルール

  • チャンク制限は、チャネルアダプター/設定から取得され、IR テキストに適用されます。
  • コードフェンス(code fence)は、単一のブロックとして保持され、チャネルが正しくレンダリングするように末尾の改行が付きます。
  • リストプレフィックスとブロッククォートプレフィックスは IR テキストの一部であるため、チャンキングはプレフィックスの途中で分割されません。
  • インラインスタイル(bold/italic/strike/inline-code/spoiler)は、チャンク間で分割されません。レンダラーは各チャンク内でスタイルを再度開きます。

チャネル間のチャンキング動作の詳細が必要な場合は、Streaming + chunking を参照してください。

リンクポリシー

  • Slack: [label](url) -> <url|label>。生の URL は生のまま。自動リンクは解析中に無効化され、二重リンクを回避します。
  • Telegram: [label](url) -> <a href="url">label</a>(HTML 解析モード)。
  • Signal: [label](url) -> label (url)(ラベルが URL に一致しない限り)。

スポイラー

スポイラーマーカー(||spoiler||)は Signal 用にのみ解析され、SPOILER スタイル範囲にマッピングされます。他のチャネルはそれらをプレーンテキストとして扱います。

チャネルフォーマッターを追加または更新する方法

  1. 一度解析: チャネル適切なオプション(autolink、見出しスタイル、ブロッククォートプレフィックス)で共有 markdownToIR(...) ヘルパーを使用します。
  2. レンダリング: renderMarkdownWithMarkers(...) とスタイルマーカーマップ(または Signal スタイル範囲)を使用してレンダラーを実装します。
  3. チャンク: レンダリング前に chunkMarkdownIR(...) を呼び出します。各チャンクをレンダリングします。
  4. アダプターを配線: チャネル送信アダプターを更新して、新しいチャンカーとレンダラーを使用します。
  5. テスト: フォーマットテストを追加または更新し、チャネルがチャンキングを使用する場合は送信配信テストを追加します。

よくある落とし穴

  • Slack アングルブラケットトークン(<@U123><#C123><https://...>)は保持する必要があります。生の HTML を安全にエスケープします。
  • Telegram HTML は、壊れたマークアップを避けるために、タグの外側のテキストをエスケープする必要があります。
  • Signal スタイル範囲は UTF-16 オフセットに依存します。コードポイントオフセットを使用しないでください。
  • フェンスコードブロックの末尾の改行を保持して、クロージングマーカーが独自の行に配置されるようにします。