概念の内部
Markdownフォーマット
OpenClawは、送信Markdownをチャネル固有の出力にレンダリングする前に、共有中間表現(IR)に変換することでフォーマットします。IRはソーステキストをそのまま保持しつつ、スタイル/リンクスパンを運ぶため、チャンキングとレンダリングがチャネル間で一貫性を保つことができます。
目標
- 一貫性: 1回の解析ステップ、複数のレンダラー。
- 安全なチャンキング: インラインフォーマットがチャンク間で決して壊れないように、レンダリング前にテキストを分割。
- チャネル適合: Markdownを再解析することなく、同じIRをSlack mrkdwn、Telegram HTML、Signalのスタイル範囲にマッピング。
パイプライン
- Markdownの解析 -> IR
- IRはプレーンテキストにスタイルスパン(太字/斜体/打ち消し線/コード/ネタバレ)とリンクスパンを加えたもの。
- オフセットはUTF-16コード単位なので、Signalのスタイル範囲がそのAPIと整合する。
- テーブルは、チャネルがテーブル変換を選択した場合にのみ解析される。
- IRのチャンキング(フォーマット優先)
- チャンキングはレンダリング前のIRテキストに対して行われる。
- インラインフォーマットはチャンク間で分割されない。スパンはチャンクごとにスライスされる。
- チャネルごとのレンダリング
- Slack: mrkdwnトークン(太字/斜体/打ち消し線/コード)、リンクは
<url|label>として。 - Telegram: HTMLタグ(
<b>、<i>、<s>、<code>、<pre><code>、<a href>)。 - Signal: プレーンテキスト +
text-style範囲。ラベルがURLと異なる場合、リンクはlabel (url)になる。
- Slack: mrkdwnトークン(太字/斜体/打ち消し線/コード)、リンクは
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テキストに適用される。
- コードフェンスは、チャネルが正しくレンダリングできるように、末尾の改行付きの単一ブロックとして保持される。
- リストプレフィックスとブロッククォートプレフィックスはIRテキストの一部であるため、チャンキングはプレフィックスの途中で分割しない。
- インラインスタイル(太字/斜体/打ち消し線/インラインコード/ネタバレ)はチャンク間で分割されない。レンダラーは各チャンク内でスタイルを再開する。
チャネル間のチャンキング動作についてさらに知りたい場合は、ストリーミング + チャンキングを参照してください。
リンクポリシー
- Slack:
[label](url)-><url|label>。生のURLはそのまま。二重リンクを避けるため、解析中は自動リンクを無効化。 - Telegram:
[label](url)-><a href="url">label</a>(HTML解析モード)。 - Signal:
[label](url)->label (url)(ラベルがURLと一致しない場合)。
ネタバレ
ネタバレマーカー(||spoiler||)はSignal用にのみ解析され、SPOILERスタイル範囲にマッピングされます。他のチャネルではプレーンテキストとして扱われます。
チャネルフォーマッターの追加または更新方法
- 一度だけ解析: チャネルに適したオプション(自動リンク、見出しスタイル、ブロッククォートプレフィックス)を指定して、共有の
markdownToIR(...)ヘルパーを使用。 - レンダリング:
renderMarkdownWithMarkers(...)とスタイルマーカーマップ(またはSignalスタイル範囲)を使用してレンダラーを実装。 - チャンキング: レンダリング前に
chunkMarkdownIR(...)を呼び出し、各チャンクをレンダリング。 - アダプターの接続: チャネル送信アダプターを更新して、新しいチャンカーとレンダラーを使用するようにする。
- テスト: フォーマットテストを追加または更新し、チャネルがチャンキングを使用する場合は送信配信テストも追加。
よくある落とし穴
- Slackの角括弧トークン(
<@U123>、<#C123>、<https://...>)は保持する必要がある。生のHTMLは安全にエスケープする。 - Telegram HTMLでは、壊れたマークアップを避けるため、タグ外のテキストをエスケープする必要がある。
- Signalのスタイル範囲はUTF-16オフセットに依存する。コードポイントオフセットを使用しない。
- 終了マーカーが単独行に配置されるように、フェンスで囲まれたコードブロックの末尾の改行を保持する。