# ワークスペース・アプリ ブリッジ仕様（アプリ作者＝エージェント向け）

ワークスペース・アプリは、ワークスペースの `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` がそのまま使われます。形式が壊れていても無視され、
フォールバックされます（アプリ一覧は壊れません）。

```json
{
  "title": "ファイル・ノート",
  "description": "output/ のファイルを閲覧してメモを保存するアプリ",
  "entry": "index.html"
}
```

| キー | 必須 | 説明 |
|---|---|---|
| `title` | 任意 | 一覧に出す表示名（無ければフォルダ名） |
| `description` | 任意 | 一覧に出す短い説明 |
| `entry` | 任意 | アプリフォルダ基準の HTML エントリ（既定 `index.html`）。**必ず `apps/{name}/` 配下**に収めること（`..` や絶対パスは拒否され、`index.html` にフォールバックされる） |

## ブリッジ・プロトコル

要求は `window.parent.postMessage(req, '*')` で送り、応答は `window` の `message` イベントで
受け取ります。各要求には一意の `id` を付け、応答の `id` と突き合わせてください。

### 要求

```js
// 読み取り
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' }, '*');
```

### 応答

```js
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）

```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 で読み込めない。
