Web interfaces
ダッシュボードアーキテクチャ
ビジョン
現在、エージェントとの作業はテキストストリームです。ダッシュボードはこれを作業台へと変えます。エージェントはライブでインタラクティブなウィジェットをレンダリングし、ユーザーはそれらを永続的なサーフェスにピン留めします。チャットは側面にドッキング(または非表示)され、ボードがメインコンテンツになります。セッションを離れることなく、「エージェントと会話する」状態から「エージェントが構築したコントロールパネルを操作する」状態へ移行できます。
原則:
- ボードはセッションの一面であり、新しいオブジェクトではありません。 すべてのセッション(スレッド)には、トランスクリプトとボードという2つの面があります。ピン留めされたウィジェットがないセッションは通常のチャットです。ウィジェットを1つピン留めすると、ボードが存在するようになります。ボードはセッションのアイデンティティ、エージェントの所有権、命名、ピン留め、ライフサイクルを継承します。
dashboard_create、ボードレジストリ、独立した ACL モデルはありません。 - エージェントとの同等性。 ユーザーがボード上で実行できるすべての操作を、エージェントもツールで実行できます。ウィジェットの追加、更新、削除、配置、タブの管理、表示タブの切り替え、チャットのドッキングや非表示が可能です。
- 埋め込みではなくネイティブ。 ボードは Control UI シェル内の Lit コンポーネントです(アプリのほかの部分と同じデザインシステム)。サンドボックス化されるのはウィジェットの_コンテンツ_のみで、URL バーやブラウザーのクロームはありません。
- 小さなエージェントサーフェス。 ウィジェットは安定した名前で指定され、インプレースで更新されます。レイアウトは流動的な自動圧縮グリッドです。エージェントが指定するのはサイズとアンカーであり、ピクセルや座標ではありません。
- 信頼よりケイパビリティ。 ウィジェットコードは、強固なサンドボックス内にあるエージェント作成の任意の HTML/JS です。アクセス範囲(Gateway データ、アクション、ネットワーク)は、宣言され、オペレーターによって許可されたケイパビリティマニフェストを通じてのみ提供されます。
概念
| 概念 | 定義 |
|---|---|
| セッション(スレッド) | 安定した sessionKey をキーとする既存の Gateway セッション。エージェントが所有します。 |
| ボード | 1つのセッションのウィジェット面。セッションにウィジェットまたはタブがある場合にのみ存在します。/new//reset 後も維持されます(トランスクリプトではなく sessionKey に紐づきます)。 |
| タブ | ボードのプレゼンテーションページ。対象ウィジェット、その配置、チャットのドック状態(left/right/bottom/hidden)を定義します。ボードは暗黙的なタブ1つから開始します。 |
| ウィジェット | セッションが所有する、名前付きでサンドボックス化された HTML/JS プログラム。sessionKey + name で指定され、名前によってインプレース更新されます。 |
| ケイパビリティマニフェスト | ウィジェットごとのアクセス範囲の宣言: data(読み取りバインディング)、actions(許可リストに登録された動詞)、prompt(セッションへの送信)、net(許可されたオリジン)。 |
| ピン(ウィジェット) | トランスクリプトのウィジェットをセッションのボードへ移動すること(ユーザー向け操作またはエージェントツールの引数)。ピン留めを解除するとボードから削除されます。 |
| ピン(セッション) | 既存のサイドバーでのセッションのピン留め。ボードを持つピン留め済みセッションは、そのボード面で開きます。 |
UX フロー
- 昇格: エージェントが任意のチャットで
show_widgetを呼び出す → ウィジェットが現在とまったく同じようにトランスクリプト内へインラインでレンダリングされる → ホバーすると ダッシュボードにピン留め が表示される → ウィジェットがセッションのボードに表示されます。エージェントはpin: trueを渡して同じ操作を実行できます。 - ボードビュー: ボードを持つセッションには表示面の切り替え(チャット / ダッシュボード)が追加されます。ボードビュー = タブストリップ(タブが2つ以上の場合のみ)+ 流動的なグリッド + ドッキングされたチャットペインです。チャットドックはサイドバーとまったく同様に、サイズ変更、移動(左/右/下)、折りたたみが可能です。ドック状態はタブごとに記憶されます。
- ドラッグ: ユーザーがウィジェットをドラッグすると、グリッドが自動圧縮されます(ウィジェットが上へ移動し、隣接要素が再配置されます)。ハンドルによるサイズ変更はサイズ刻みにスナップします。誰に対してもピクセル単位の配置はありません。
- リセット警告: ボードを持つセッションで
/new//resetを実行すると、Web UI で確認(「コンテキストはリセットされますが、ダッシュボードは維持されます」)が求められ、ボードは保持されます。 - サイドバー: ピン留めされたセッションにボードがある場合、そのボード面がレンダリングされます。Home セッションのボードがデフォルトの「エージェントダッシュボード」です。
- インタラクション(以下の3階層を参照): サイレント状態イベント、可視プロンプト送信、自動化トリガー。
インタラクション階層
- 状態イベント(デフォルト)。 モデルが認識すべきものの、応答する必要はないウィジェット UI のインタラクションです。
bridge.emitState({...})は、構造化されたセッション通知を追加します(グループアクティビティ通知と同じ仕組み)。エージェントターンは開始されず、モデルは次回の実行時に蓄積された通知を参照します。 - プロンプト(明示的な会話)。
bridge.sendPrompt(text)— ユーザーによる操作が必要です。可視のユーザーメッセージをセッションへ送信します(ドッキングされたチャットに表示されます)。レート制限が適用され、ウィジェットにpromptケイパビリティの許可がない限り、送信ごとにユーザーの確認が必要です。 - 自動化。
bridge.runAction(name, args)— マニフェストで宣言されたアクションを起動します。初期の動詞セット:cron.trigger(既存の Cron ジョブを今すぐ実行)とbinding.refresh。Cron ジョブはすでに可視の分離された実行セッションで動作し、より低コストのモデルを使用できます。これが「小規模モデルがウィジェットを動かす」経路です。非表示のセッションは一切ありません。
ウィジェットモデルとホスティング
ウィジェットの HTML/JS はエージェントによって作成され(通常は show_widget を使用)、標準ドキュメントシェル(CSP メタ、サイズレポーター、ブリッジブートストラップ)でラップされ、<iframe sandbox="allow-scripts"> 内にレンダリングされます(allow-same-origin には決してレンダリングされません)。
- インライン(トランスクリプト)ウィジェットは、現在のキャンバスドキュメントパイプラインを維持します。状態ディレクトリ配下に書き込まれ、Gateway によって提供され、スコープごとに削除され、承認は不要です(構造上ケイパビリティを持たず、プロンプト送信はユーザーによって確認されます)。
- ボードウィジェットはセッション状態です。バイト列は所有元エージェントの SQLite DB(
board_widgets)に格納され、その DB を読み取るコア Gateway ルート(/__openclaw__/board/<agentId>/<sessionKey>/<name>/)によって提供されます。トランスクリプトのウィジェットをピン留めすると、バイト列がコピーされます。上限: ウィジェットごとに 256 KB、ボードごとに 48 ウィジェット。 - インプレース更新: 同じ
nameでウィジェットを再生成すると、バイト列が置き換えられ、revisionが増加し、board.changedがブロードキャストされ、ライブビューではその iframe のみが再読み込みされます。 - バイト固定: 許可されたケイパビリティは、ウィジェットのバイト列の sha256 に紐づきます。バイト列を変更した場合、新しいリビジョンが許可済みマニフェストのサブセットを宣言している場合に限り、
data/net/actionsの許可が維持されます。マニフェストの範囲が拡大した場合は、オペレーターに再確認します。
ウィジェットがコンテンツをホストし、MCP アプリはその一種
ウィジェットが OpenClaw のプリミティブです。これは名前付きでピン留めされ、サイズが指定された、セッション所有のボードセルであり、許可レコードを持ちます。その内部にレンダリングされるものがコンテンツ種別です。
html—show_widgetを使用してエージェントが作成し、バイト列はボードストレージに格納されます。mcp-app— ウィジェットセル内でホストされる、構成済みサーバーからのサードパーティ MCP アプリビュー(ui://リソース)。
MCP アプリがウィジェットモデルを定義するのではなく、ウィジェットが MCP アプリをホストできるようになったものです。アイデンティティ、配置、ピン留め、許可、作成者向け API は引き続き OpenClaw が所有します。そのため、show_widget コードは現在と同じく簡潔なままで、MCP Apps 仕様の存在を知る必要はありません。
基盤となる共有インフラストラクチャ(ここで簡素化が実現されます):
- 1つのサンドボックスホスト。
htmlウィジェットは、独自の iframe ホストを別途用意する代わりに、MCP アプリとともに提供された同じ強化済みパイプライン(専用サンドボックスオリジン上の二重 iframe、ウィジェットごとに宣言され、フェイルクローズでデコードされる CSP)を通じてレンダリングされます。プロキシは HTML を値として受け取るため、ローカルコンテンツが自然なケースです。 - 1つの認可モデル。 ウィジェットの種類にかかわらず、そのアクセス範囲は許可された許可リストです。
htmlウィジェットの場合はホストツール、mcp-appウィジェットの場合はサーバーのアプリ可視ツール(既存のallowedAppToolNamesの仕組みを介し、生成実行ごとではなくウィジェットごとに永続化されます)です。 htmlウィジェット用ホストツール(ウィジェットブリッジ経由で公開され、許可に照らして検証されます):openclaw.prompt.send— 階層2。可視のコンポーザーを経由し、許可されていない限りユーザーによる確認が必要ですopenclaw.state.emit— 階層1のセッション通知(統合され、サイズ上限が適用されます)openclaw.data.read— パラメーター化された読み取り専用バインディング(既存の許可リスト登録済み読み取り RPC セット)。Gateway 側で解決されますopenclaw.cron.trigger— 階層3の自動化
net= CSP。 ネットワークへのアクセス範囲には、すでに提供されているウィジェットごとの CSP 宣言(connect-srcオリジン)を使用します。自己更新型の天気ウィジェットはサンドボックスから API を直接取得し、Gateway は関与しません。- 許可。 何も宣言しないウィジェットは即座にレンダリングされます(サンドボックス化、
default-src 'none'、プロンプト送信は個別に確認)。現在のインラインチャットウィジェットと同じ信頼レベルです。ツールやオリジンを宣言すると、ウィジェットはボード上でpendingになります。プレースホルダーカードには、それらが人間に読みやすい形式で一覧表示され、ワンタップの 許可/拒否 が用意されます。許可はウィジェット名ごとに設定されます。htmlウィジェットではバイト固定(sha256)され、バイト列が変更された場合、宣言の範囲が縮小した場合にのみ許可が維持されます。 - 作成用シム。 ドキュメントラッパーは、安定した作成者向け API として
window.openclaw.prompt、window.openclaw.state、window.openclaw.data、window.openclaw.cronを注入します。ダッシュボード呼び出しは、ビューチケットに紐づいた1つのリクエストチャネルを共有します。サイズレポートとテーマトークンは、引き続き別々のホスト通知です。
Plugin のケイパビリティ宣言
有効な Plugin は、openclaw.plugin.json 内の dashboard.dataBindings と dashboard.actionVerbs を通じてウィジェットホストを拡張できます。Plugin ローカル ID は、workboard.cards.list や workboard.dispatch のように、Plugin ID を接頭辞とする許可名になります。別の Plugin/ローカル ID の分割が同じ永続化された許可を継承できないように、Plugin ID セグメント内の % と . はエスケープされます。Plugin の登録時に、OpenClaw はすべてのバインディングが同じ Plugin によって operator.read で登録された RPC を対象としていること、およびすべてのアクションが operator.write で登録された RPC を対象としていることを検証します。無効な宣言があると、Plugin の読み込みに失敗します。検証済みレジストリは Plugin のライフサイクル変更時にのみ再構築されますが、ウィジェットの許可はウィジェットごとに維持され、バイト列とリビジョンに紐づきます。
モデル化された残存リスク: WebRTC データチャネル
サンドボックス CSP は提案されている webrtc 'block' ディレクティブを出力しますが、Chromium の現在の CSP ディレクティブセットでは実装されていません。そのため、現在の Chromium では、スクリプト実行可能なウィジェットが WebRTC データチャネルを外部送信に使用できます。同じ残存リスクは、インラインチャットウィジェットと main 上の MCP Apps ホストですでに存在します。
受け入れるトレードオフ: OpenClaw は、この残余リスクを理由にスクリプト可能なウィジェットを制限しません。ウィジェットのコンテンツが機密性の高い OpenClaw データへアクセスできるのは、オペレーターが付与したバイト固定済みの data:read capability を介する場合に限られ、サンドボックスの Permissions Policy はカメラとマイクへのアクセスをブロックします。DOM API ガードはベストエフォートの多層防御であり、セキュリティ境界ではないため、後続の堅牢化で対応します。
トランスクリプト表示: 1 枚のウィジェットカード
インライン表示はウィジェットプリミティブに統一されます。ツール結果に UI(show_widget の出力、またはアプリリソースを含む MCP ツール結果)が含まれる場合、システムは一時的な、自動命名されたウィジェット(セッションスコープ、後に削除)を実体化し、トランスクリプトはコンテンツ種別に応じて処理を振り分ける単一のウィジェットカードをレンダリングします。MCP アプリの自動表示は仕様の期待どおりのままです(モデルによる追加処理はゼロ)。その基盤がウィジェットになるだけです。これにより、チャットレンダリング内で並行して存在する mcpApp の特殊処理(サーフェス制限、個別の重複排除)が削除され、すべてのインライン UI に同じピン留め操作が提供され、ウィジェットレジストリが主要な再オープン経路になります(ピン留めされたことのない履歴については、トランスクリプト走査による再構築がフォールバックとして残ります)。読み取り専用のチケット付きスタンドアロンホストは、永続的な再オープンサーフェスとしてボードと重複します。これは T6 で評価する統合候補であり、統合を前提とはしません。
合成: v1 はグリッド上の隣接配置です(1 つのタブで、エージェント chrome ウィジェットをアプリウィジェットの隣に配置)。v2 ではホスト管理のアプリスロットが追加されます。エージェントウィジェットの HTML がスロット領域を宣言し、ホストが実際のアプリビューを兄弟サンドボックスとして合成します。アプリがエージェントの iframe 内でレンダリングされることはありません。ネストするとブリッジの ID が壊れ、許可されたアプリ UI に対するオーバーレイやクリックジャッキングが可能になるため、スロットは埋め込みではなくレイアウト契約です。
サーバーをソースとするウィジェット(ピン留めされた MCP アプリ)
統一ホストでは、サードパーティ製 MCP アプリのピン留めは、コンテンツを保存する代わりにサーバーから取得するウィジェットにすぎません。board_widgets は HTML バイトではなく、ディスクリプター(serverName、toolName、uiResourceUri、生成元の toolCallId + sessionKey)を保持し、ボードはチャットターンの 10 分間の TTL を超えてビューリースを再発行します(古くなった場合は ui:// リソースを再取得)。チャット内のインライン MCP アプリビューには、エージェントウィジェットと同じダッシュボードにピン留め操作が提供されます。再オープンされたビューは現在、設計上読み取り専用です。インタラクティブな状態を維持すべきピン留め済みアプリには、サーバーのアプリ可視ツールに対する永続的な許可(ピン留め時に明示的な許可リストをオペレーターへ表示)が付与され、発行元の実行から切り離されます。許可されていないピン留めは読み取り専用のままですが、表示用ダッシュボードには引き続き有用です。v1 では生成元セッションのボードにピン留めします。セッションをまたぐピン留めにはリースブローカーが必要なため、後回しにします。オープン PR #109807(ui/message コンポーザールーティング、テーマ/サイズの伝播)と調整します。
WorkBoard 連携
WorkBoard 連携プログラムでは、カードとボードの所有権を Plugin に維持しつつ、既存の sessionKey と runId を介して、ディスパッチされたカードを対応するセッションボードへ結び付けます。また、Plugin が宣言するバインディングとアクションを通じて WorkBoard のフィードとディスパッチを公開し、WorkBoard 固有のウィジェット種別を導入する代わりに、それらの結果を既存の html および mcp-app ウィジェット種別と合成します。
レイアウト: 流動グリッド
12 列、固定行高、自動圧縮(上方向への重力、ドラッグ時の押し退け。gridstack のセマンティクスをネイティブ実装。グリッド計算は純粋かつ DOM 非依存のまま)。タブごとのウィジェットレイアウト状態: { name, w (1-12), h (rows) } と順序。エージェント用語:
size:sm(3×3) ·md(6×4) ·lg(8×6) ·xl(12×8) ·full(単一ウィジェットのタブ)after: <widgetName>は省略可能な順序アンカー。省略時は末尾に追加- ユーザーは自由にドラッグ/サイズ変更でき、同じ順序+サイズモデルでラウンドトリップします。
データモデル(エージェントごとの DB)
agents/<agentId>/agent/openclaw-agent.sqlite に新しいテーブルを追加
(エージェント DB のスキーマバージョン更新が必要です。この変更を取り込む前にオペレーターの承認が必要です):
CREATE TABLE board_tabs ( session_key TEXT NOT NULL, tab_id TEXT NOT NULL, -- slug title TEXT NOT NULL, position INTEGER NOT NULL, chat_dock TEXT NOT NULL DEFAULT 'right', -- left|right|bottom|hidden created_by TEXT NOT NULL, -- 'user' | 'agent' PRIMARY KEY (session_key, tab_id)) STRICT; CREATE TABLE board_widgets ( session_key TEXT NOT NULL, name TEXT NOT NULL, -- stable widget name tab_id TEXT NOT NULL, title TEXT, html BLOB NOT NULL, -- wrapped document source sha256 TEXT NOT NULL, revision INTEGER NOT NULL, size_w INTEGER NOT NULL, size_h INTEGER NOT NULL, position INTEGER NOT NULL, -- order within tab (auto-compact input) manifest TEXT NOT NULL DEFAULT '{}', -- capability manifest JSON grant_state TEXT NOT NULL DEFAULT 'none', -- none|pending|granted|rejected granted_sha TEXT, -- byte-frozen grant created_by TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, PRIMARY KEY (session_key, name)) STRICT;ボードの存在 = sessionKey に対応する行が 1 つでも存在すること。セッションを削除すると、そのボード行も削除されます。/new//reset はそれらに影響しません。
プロトコルサーフェス
RPC(コアメソッドテーブル、gateway-protocol 内の typebox スキーマ):
board.get { sessionKey }→ タブ+ウィジェットメタデータ(バイトなし)—operator.readboard.update { sessionKey, ops[] }— タブの CRUD/並べ替え、ウィジェットの移動/サイズ変更/ 削除/ピン留め解除、ドック状態、タブへのフォーカス —operator.writeboard.widget.put { sessionKey, name, html, manifest, placement }—operator.write(エージェントツール経路およびピン留め経路)board.widget.grant { sessionKey, name, decision }—operator.approvalsboard.event { ticket, payload }— チケットに紐付いた tier-1 状態イベントの取り込み。 従来の信頼済みホスト向け{ sessionKey, widget, payload }形式は維持 —operator.writeboard.prompt.authorize { ticket }— 表示中のプロンプト送信に クリックごとの確認がまだ必要かどうかを返す —operator.readboard.data.read { ticket, bindingId, params? }— Gateway 側で許可リストに登録された コアまたはアクティブな Plugin の読み取りバインディング解決 —operator.readboard.action { ticket, action, ... }— 既存の Cron 即時実行経路、またはアクティブな Plugin の検証済みアクション動詞を 通じた、許可と完全に一致する自動化ディスパッチ —operator.write
イベント(EVENT_SCOPE_GUARDS 内、読み取りスコープ):
board.changed { sessionKey, revision, widget? }— 永続化された状態が変更された。 UI は再取得します(widgetが存在する場合は 1 つの iframe も再読み込み)。board.command { sessionKey, command }— 一時的な UI 操作(エージェントが 表示中のタブを切り替える、チャットドックを切り替える)—ui.commandパターン。
ウィジェットのバイト列はソケットではなく、認証済み HTTP サーフェスを介して提供されます。
エージェントツール
合計 3 つのツール(コア、常時登録。レンダリングは現在と同様に
inline-widgets クライアント capability によって制限):
show_widget { title, widget_code, name?, pin?, size?, tab?, after?, capabilities? }— 名前による作成/更新。pinがボード上に配置します。name/pinがない場合は、現在とまったく同じように動作します(インライン、一時的)。dashboard { action, ... }— ボード管理動詞:read、tab_create、tab_update、tab_delete、tabs_reorder、widget_move、widget_remove、unpin、focus_tab、set_chat_dock。- 既存の
cronツールが自動化 tier を扱うため、新しいツールは不要です。
ツールの説明では、サイズ/アンカーの用語と tier モデルを示します。エージェントには、たとえば
[dashboard] user clicked "Refresh" on widget weather (tab main) のようなセッション通知を通じて、ユーザーの tier-1 イベントが通知されます。
置き換えるもの
extensions/workspacesは削除されます。 実験的なenabledByDefault: falseであり、安定版リリースには一度も含まれていません(2026.7.2 ベータ版で初登場)。移行は行いません。存在する場合は doctor ルールが古い<stateDir>/workspaces/を削除します。 取り入れたアイデア: 純粋なグリッド計算、ブリッジセキュリティモデル(ポートのブートストラップ、 バインディング制限、レート制限)、バイト固定された承認。- ウィジェットホスティングを
extensions/canvasからコアへ移します。 canvas ドキュメント ストア、ドキュメントラッパー、HTTP 提供、およびshow_widgetツールはコア (src/canvas/)になり、Plugin には node-canvas 制御ツール(canvas)と A2UI が残ります。pluginSurfaceUrls["canvas"]の広告と/__openclaw__/canvasの経路は、リリース済みのネイティブクライアント契約であるため 安定したまま維持します。Discord セッションでは、Discord が所有するshow_widgetバリアントを維持します。
非目標(このプログラム)
- 複数ユーザー間のボード共有/ACL(将来対応。セッション共有を通じて提供予定)。
- ネイティブ macOS/iOS のボードレンダリング(Control UI を埋め込む場所で利用可能。 インラインウィジェット経路は変更なし)。
- 組み込みデータウィジェット(セッション/使用量/Cron カード)— capability ブリッジと エージェント作成ウィジェットで v1 をカバーします。組み込み種別レジストリは後から追加できます。
実装計画
独立したワークツリーで Codex により構築し、順番にレビュー+取り込みを行います。取り込んでから修正します。
| # | ブランチ | スコープ | 依存関係 |
|---|---|---|---|
| T1 | claude/dashboard-remove-workspaces |
workspaces Plugin+UI+ドキュメント+i18n キーを削除。doctor クリーンアップルール | — |
| T2 | claude/dashboard-canvas-core |
ウィジェットホスティング+show_widget をコアへ昇格。canvas Plugin は Node ツールを維持。動作変更はゼロ |
— |
| T3 | claude/dashboard-domain |
エージェント DB テーブル(スキーマ更新)、board.* RPC+イベント、dashboard ツール、show_widget のピン留め/名前/manifest 引数、tier-1 通知、リセット後もボードを維持 |
T2 |
| T4 | claude/dashboard-ui |
ボード画面+タブストリップ+流動的な自動圧縮グリッド+チャットドック(左/右/下/非表示)+トランスクリプトのピン留め操作+サイドバーのボード画面+リセット確認 | T3(開発フィクスチャによるモック優先) |
| T5 | claude/dashboard-capabilities |
許可ストア/UI+バイト固定。html ウィジェットを共有サンドボックスホストへ移動。ホストツール(openclaw.prompt.send/state.emit/data.read/cron.trigger)。net CSP。オーサリング shim |
T3、T4 |
| T7 | claude/dashboard-mcp-apps |
mcp-app コンテンツ種別: インラインアプリビューのピン留め操作、ディスクリプター保存、リースの再発行/更新、永続的なサーバーツール許可(リリース済み MCP Apps ホストを再利用) |
T3、T4 |
| T6 | 仕上げ | スクラッチ Gateway(実キー)でのライブ E2E、スクリーンショット、修正、ユーザー重視の /web/dashboard 書き直し、デフォルト有効化のレビュー |
すべて |
リポジトリのルールに従った検証: ローカルで対象を絞った vitest、Crabbox/Testbox で全ゲート、各取り込み前に $autoreview、T6 ではライブ検証。