Concept internals
Markdown の書式設定
OpenClaw は、チャネル固有の出力をレンダリングする前に、送信する Markdown を共有中間表現 (IR)へ変換します。IR はプレーンテキストとスタイル/リンクのスパンを保持するため、 1 回の解析ですべてのチャネルに対応でき、チャンク分割によって スパンの途中で書式が分断されることもありません。
パイプライン
- Markdown を IR に解析(
markdownToIR)- プレーンテキストとスタイルスパン (太字、斜体、取り消し線、コード、コードブロック、スポイラー、引用ブロック、 見出し 1~6)、およびリンクスパン。オフセットは UTF-16 コード単位なので、Signal のスタイル 範囲をその API に直接合わせられます。テーブルは、チャネルが テーブルモードを有効にした場合のみ解析されます。 - IR をチャンク分割(
chunkMarkdownIR/renderMarkdownIRChunksWithinLimit)- 分割はレンダリング前の IR テキストに対して行われるため、インラインスタイルと リンクは境界をまたいで分断されず、チャンクごとに切り分けられます。
- チャネルごとにレンダリング(
renderMarkdownWithMarkers)- スタイルマーカーのマップが スパンをチャネルのネイティブマークアップに変換します。
| チャネル | レンダラー | 備考 |
|---|---|---|
| Slack | mrkdwn トークン(*bold*、_italic_、`code`、コードフェンス) |
リンクは <url|label> になります。二重リンクを避けるため、解析時は自動リンクが無効です |
| Telegram | HTML タグ(<b>、<i>、<s>、<code>、<pre><code>、<a href>、<tg-spoiler>) |
richMessages がオンの場合、リッチメッセージのテーブルと見出し(<h1>~<h6>)にも対応します |
| Signal | プレーンテキスト + text-style 範囲 |
ラベルが URL と異なる場合、リンクは label (url) としてレンダリングされます |
| Discord、WhatsApp、iMessage、Microsoft Teams、およびその他のチャネル | プレーンテキスト | IR ベースのスタイルはありません。Markdown テーブルの変換は引き続き convertMarkdownTables を介して実行されます |
IR の例
入力 Markdown:
こんにちは **世界** - [ドキュメント](https://docs.openclaw.ai)を参照してください。IR(概略):
{ "text": "こんにちは 世界 - ドキュメントを参照してください。", "styles": [{ "start": 6, "end": 11, "style": "bold" }], "links": [{ "start": 19, "end": 23, "href": "https://docs.openclaw.ai" }]}テーブルの処理
markdown.tables は、チャネルが Markdown テーブルを変換する方法を、チャネルごと、
必要に応じてアカウントごとに制御します。
| モード | 動作 |
|---|---|
code |
コードブロック内で整列された ASCII テーブルとしてレンダリングします(フォールバックのデフォルト) |
bullets |
各行を label: value の箇条書き項目に変換します |
block |
トランスポートが対応している場合はネイティブテーブルを維持し、それ以外は code にフォールバックします |
off |
テーブル解析を無効にし、生のテーブルテキストを変更せずに渡します |
チャネル別 Plugin のデフォルト:Signal、WhatsApp、Matrix のデフォルトは
bullets、Mattermost のデフォルトは off、Telegram のデフォルトは block です(アカウントで
richMessages が有効でない限り、code に解決されます)。明示的な Plugin のデフォルトがない
チャネルは code にフォールバックします。
channels: discord: markdown: tables: code accounts: work: markdown: tables: offチャンク分割のルール
- チャンク制限はチャネルアダプター/設定から取得され、レンダリングされた出力ではなく IR テキストに適用されます。
- フェンス付きコードブロックは、チャネルが終了フェンスを正しくレンダリングできるよう、 末尾の改行を含む 1 つのブロックとして維持されます。
- リストと引用ブロックの接頭辞は IR テキストの一部であるため、チャンク分割によって 接頭辞の途中で分断されることはありません。
- インラインスタイルがチャンク間で分断されることはありません。レンダラーは、次のチャンクの 先頭で継続中のスタイルを再開します。
チャネル間のチャンク境界と配信動作については、ストリーミングとチャンク分割を 参照してください。
リンクポリシー
- Slack:
[label](url)→<url|label>。裸の URL はそのまま維持されます。 - Telegram:
[label](url)→<a href="url">label</a>(HTML 解析モード)。 - Signal: ラベルがすでに URL と一致している場合を除き、
[label](url)→label (url)。
スポイラー
スポイラーマーカー(||spoiler||)は Signal(SPOILER の
スタイル範囲にマッピング)と Telegram(<tg-spoiler> にマッピング)で解析されます。その他のチャネルでは、
||...|| はプレーンテキストとして扱われます。
チャネルフォーマッターの追加または更新
- チャネルに適したオプション(
autolink、headingStyle、blockquotePrefix、tableMode)を渡し、markdownToIR(...)で一度だけ解析します。 - スタイルマーカーのマップ(または
Signal のようなトランスポート向けのカスタムスタイル範囲ロジック)と
renderMarkdownWithMarkers(...)でレンダリングします。 - 各チャンクをレンダリングする前に、
chunkMarkdownIR(...)またはrenderMarkdownIRChunksWithinLimit(...)でチャンク分割します。 - 送信処理から新しいチャンク分割処理とレンダラーを呼び出すよう、アダプターを接続します。
- チャネルがチャンク分割を行う場合は、書式テストと送信配信テストでテストします。
よくある落とし穴
- Slack の山括弧トークン(
<@U123>、<#C123>、<https://...>)は エスケープ後も維持する必要があります。生の HTML も引き続き安全にエスケープする必要があります。 - Telegram HTML では、マークアップの破損を避けるため、タグ外のテキストをエスケープする必要があります。
- Signal のスタイル範囲は、コードポイントのオフセットではなく UTF-16 オフセットを使用します。
- 終了マーカーが独立した行に配置されるよう、フェンス付きコードブロックの末尾の改行を維持します。
関連項目
Was this useful?