7.4 KiB
ワークスペース・アプリ ブリッジ仕様(アプリ作者=エージェント向け)
ワークスペース・アプリは、ワークスペースの apps/{name}/index.html に置く HTML 製の小さな
GUI です。ワークスペースの 「アプリ」タブに一覧表示され、「開く」を押すと AppRunner が
サンドボックス iframe で実行します(ファイルタブの「アプリとして実行」からも起動できます)。
アプリはワークスペースのファイルを postMessage ブリッジ経由でのみ 読み書きできます。
このドキュメントは、生成する HTML(アプリ)が従うべきブリッジ API を定めます。
エージェントへ: ユーザーから「ワークスペース・アプリを作って」と頼まれたら、この仕様に 従った自己完結 HTML を
apps/{name}/index.htmlに Write してください(外部リソース禁止・ インライン JS/CSS のみ)。動くひな型はdocs/examples/workspace-apps/note-editor/(index.html+app.json)。この仕様自体はReadToolDoc({ name: "workspace-apps" })でいつでも読めます。
実行環境(重要な制約)
- アプリは
<iframe sandbox="allow-scripts">で動きます。allow-same-originは付きません。 そのため不透明オリジン(opaque origin)で動作し、親ウィンドウの DOM・Cookie・セッション・ localStorage には一切アクセスできません。 - 資格情報は渡されません。 ファイル I/O は親が代理します。代理は実行中ユーザーのセッションで 行われるため、アプリはそのユーザーの権限を超えられません(メンバーシップ・編集権限・パス ガードはサーバ側でそのまま効きます)。
- 外部ネットワークは使えません。
fetch/XMLHttpRequestによる外部通信は想定外です (配信はサンドボックス + CSP で絞られます)。データの入出力は必ずブリッジ経由で行ってください。 - V1 の主対象は 単一の自己完結
index.html(JS/CSS をインライン化したもの)。複数ファイル 構成(apps/{name}/配下の相対参照)はベストエフォートで、相対src/hrefは親が raw URL に 書き換えます。確実に動かしたいなら 1 ファイルにまとめてください。
マニフェスト app.json(任意)
アプリフォルダに apps/{name}/app.json を置くと、「アプリ」タブの表示情報を指定できます。
任意で、無ければフォルダ名と index.html がそのまま使われます。形式が壊れていても無視され、
フォールバックされます(アプリ一覧は壊れません)。
{
"title": "ファイル・ノート",
"description": "output/ のファイルを閲覧してメモを保存するアプリ",
"entry": "index.html"
}
| キー | 必須 | 説明 |
|---|---|---|
title |
任意 | 一覧に出す表示名(無ければフォルダ名) |
description |
任意 | 一覧に出す短い説明 |
entry |
任意 | アプリフォルダ基準の HTML エントリ(既定 index.html)。必ず apps/{name}/ 配下に収めること(.. や絶対パスは拒否され、index.html にフォールバックされる) |
ブリッジ・プロトコル
要求は window.parent.postMessage(req, '*') で送り、応答は window の message イベントで
受け取ります。各要求には一意の id を付け、応答の id と突き合わせてください。
要求
// 読み取り
window.parent.postMessage({ id, type: 'readFile', path: 'output/data.json' }, '*');
// 一覧(dir 省略でワークスペース直下)
window.parent.postMessage({ id, type: 'listFiles', dir: 'output' }, '*');
// 書き込み(content は文字列)
window.parent.postMessage({ id, type: 'writeFile', path: 'output/請求書.html', content: '<html>…' }, '*');
// 削除(常に確認ダイアログが出る)
window.parent.postMessage({ id, type: 'deleteFile', path: 'output/old.txt' }, '*');
応答
window.addEventListener('message', (e) => {
const r = e.data; // { id, ok, data } または { id, ok:false, error }
if (r.id !== myRequestId) return;
if (r.ok) { /* r.data を使う */ } else { /* r.error を表示 */ }
});
応答ペイロード:
| type | 成功時 data |
|---|---|
readFile |
{ path, content }(content は UTF-8 テキスト) |
listFiles |
{ dir, entries }(entries はファイル/フォルダ) |
writeFile |
{ path, bytes } |
deleteFile |
{ deleted, skipped } |
失敗時は { id, ok:false, error }。
パスの規則
- パスは必ず ワークスペース files 直下からの相対パス。先頭
/・..・ドライブ文字 (C:)・バックスラッシュは拒否されます(クライアントとサーバの二重ガード)。 - アプリは自分のワークスペースの外には出られません。
書き込みの確認ポリシー
- 確認なしで書ける場所:
output/配下、および自分のアプリのapps/{name}/data/配下。 - それ以外のパスへの
writeFileは、親が確認ダイアログ(「アプリ『{name}』が {path} に書き込もう としています。許可しますか?」)を出し、ユーザーが許可したときだけ書き込みます。 deleteFileは場所に関わらず常に確認ダイアログが出ます。
データを残すアプリは、原則として output/(ユーザーに見せる成果物)か
apps/{name}/data/(アプリ内部の状態)に書いてください。そうすればユーザーを確認で煩わせません。
最小サンプル(自己完結 index.html)
<!doctype html><meta charset="utf-8">
<button id="save">保存</button>
<script>
let seq = 0;
const pending = new Map();
function call(req) {
return new Promise((resolve, reject) => {
const id = ++seq;
pending.set(id, { resolve, reject });
window.parent.postMessage({ ...req, id }, '*');
});
}
window.addEventListener('message', (e) => {
const r = e.data;
const p = pending.get(r && r.id);
if (!p) return;
pending.delete(r.id);
r.ok ? p.resolve(r.data) : p.reject(new Error(r.error));
});
document.getElementById('save').onclick = async () => {
await call({ type: 'writeFile', path: 'output/note.txt', content: 'hello' });
alert('保存しました');
};
</script>
ネットワーク制約(重要)
アプリ HTML には default-src 'none'; connect-src 'none'; ... の CSP が自動注入され、
外部ネットワーク(fetch/XHR/WebSocket/sendBeacon/フォーム外部 POST)はすべて遮断される。
アプリのファイル I/O は postMessage ブリッジ(親が代理)だけが経路。これは、ユーザーから
渡されたワークスペースのファイル内容を外部へ持ち出されないための exfil 対策。
V1 のアプリは 自己完結 HTML(インライン JS/CSS、data: 画像)で書くこと。
外部 CDN・外部 API・別ファイルの <script src> は CSP で読み込めない。