Concept internals
Markdown の書式設定
OpenClaw は、送信 Markdown をチャネル固有の出力としてレンダリングする前に、共有の中間表現 (IR) へ変換して整形します。IR は元のテキストをそのまま保ちながら、スタイル/リンクのスパンを保持するため、チャンク分割とレンダリングをチャネル間で一貫させられます。
目標
- 一貫性: 1 回の解析ステップで、複数のレンダラーに対応。
- 安全なチャンク分割: レンダリング前にテキストを分割するため、インライン書式がチャンクをまたいで壊れません。
- チャネルへの適合: Markdown を再解析せずに、同じ IR を Slack mrkdwn、Telegram HTML、Signal スタイル範囲へマッピングします。
パイプライン
- Markdown を解析 -> IR
- IR はプレーンテキストに、スタイルスパン (bold/italic/strike/code/spoiler) とリンクスパンを加えたものです。
- オフセットは UTF-16 コード単位なので、Signal スタイル範囲はその API と一致します。
- テーブルは、チャネルがテーブル変換を有効にした場合にのみ解析されます。
- IR をチャンク分割 (書式優先)
- チャンク分割は、レンダリング前に IR テキスト上で行われます。
- インライン書式はチャンクをまたいで分割されません。スパンはチャンクごとに切り出されます。
- チャネルごとにレンダリング
- Slack: mrkdwn トークン (bold/italic/strike/code)、リンクは
<url|label>。 - Telegram: HTML タグ (
<b>,<i>,<s>,<code>,<pre><code>,<a href>)。 - Signal: プレーンテキスト +
text-style範囲。ラベルが異なる場合、リンクはlabel (url)になります。
- Slack: mrkdwn トークン (bold/italic/strike/code)、リンクは
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、Microsoft Teams、Discord) は、引き続きプレーンテキストまたは独自の書式ルールを使い、有効な場合はチャンク分割前に Markdown テーブル変換を適用します。
テーブル処理
Markdown テーブルはチャットクライアント間で一貫してサポートされているわけではありません。チャネルごと (およびアカウントごと) の変換を制御するには markdown.tables を使います。
code: テーブルをコードブロックとしてレンダリングします (ほとんどのチャネルのデフォルト)。bullets: 各行を箇条書きに変換します (Matrix、Signal、WhatsApp のデフォルト)。off: テーブルの解析と変換を無効化します。生のテーブルテキストがそのまま渡されます。
設定キー:
channels: discord: markdown: tables: code accounts: work: markdown: tables: offチャンク分割ルール
- チャンク制限はチャネルアダプター/設定から取得され、IR テキストに適用されます。
- コードフェンスは、チャネルが正しくレンダリングできるように、末尾の改行を含む単一のブロックとして保持されます。
- リスト接頭辞と blockquote 接頭辞は IR テキストの一部なので、チャンク分割で接頭辞の途中が分断されません。
- インラインスタイル (bold/italic/strike/inline-code/spoiler) はチャンク間で分割されません。レンダラーは各チャンク内でスタイルを再度開きます。
チャネル間のチャンク分割動作について詳しくは、ストリーミング + チャンク分割 を参照してください。
リンクポリシー
- Slack:
[label](url)-><url|label>。ベア URL はベアのままです。二重リンク化を避けるため、解析中の autolink は無効化されます。 - Telegram:
[label](url)-><a href="url">label</a>(HTML 解析モード)。 - Signal:
[label](url)-> ラベルが URL と一致しない限りlabel (url)。
スポイラー
スポイラーマーカー (||spoiler||) は Signal の場合にのみ解析され、SPOILER スタイル範囲へマッピングされます。その他のチャネルではプレーンテキストとして扱われます。
チャネルフォーマッターを追加または更新する方法
- 1 回だけ解析: チャネルに適したオプション (autolink、見出しスタイル、blockquote 接頭辞) とともに、共有の
markdownToIR(...)ヘルパーを使います。 - レンダリング:
renderMarkdownWithMarkers(...)とスタイルマーカーマップ (または Signal スタイル範囲) を使ってレンダラーを実装します。 - チャンク分割: レンダリング前に
chunkMarkdownIR(...)を呼び出し、各チャンクをレンダリングします。 - アダプター接続: 新しいチャンク分割器とレンダラーを使うように、チャネル送信アダプターを更新します。
- テスト: 書式テストを追加または更新し、チャネルがチャンク分割を使う場合は送信配信テストも追加します。
よくある落とし穴
- Slack の山括弧トークン (
<@U123>,<#C123>,<https://...>) は保持する必要があります。生の HTML は安全にエスケープしてください。 - Telegram HTML では、壊れたマークアップを避けるため、タグ外のテキストをエスケープする必要があります。
- Signal スタイル範囲は UTF-16 オフセットに依存します。コードポイントオフセットは使わないでください。
- フェンス付きコードブロックでは、閉じマーカーが独立した行に配置されるように、末尾の改行を保持してください。