本站有一塊叫沙盒的頁面,路徑 /playgrounds/(程式裡叫 Playgrounds)。目標很具體:在瀏覽器裡編輯多檔單頁小程式(SAM=Single-page Application Module)、立刻在畫布看到結果,必要時再掛 edge functions 形的 /api/*。同一套 OPFS 專案還可以換角色——當工作專案改、當工具掛進編輯器槽、或設為現行 Agent——再加上 REPL 與仿命令列的 Shell。資料權威在本機 OPFS,本站不當專案雲端硬碟;個人可累積的工具箱也長在這裡,而不是往站上 /tools/ 再塞登錄表。
這篇整理它怎麼疊起來。要手玩請直接開沙盒;這裡不逐步教學。
目錄
展開目錄
遊樂場、工作專案、畫布
介面上大致三塊:
| 區塊 | 做什麼 |
|---|---|
| 遊樂場介面 | CodeMirror 編輯、檔案樹、匯入/匯出專案(.sam 專案包裹)、把當前專案快照餵給畫布 |
| 工作專案 | OPFS 裡的一組檔案;權威儲存,可含文字與二進位 |
| 畫布 | 同源 iframe,用 URL /playgrounds/canvas/<projectId>/… 載入,像從網路上開一個站 |
畫布入口固定專案根目錄的 index.html。相對路徑的 ESM、CSS、圖、字型走瀏覽器原生解析,不必每次在遊樂場手改模組路徑。
早期若用 srcdoc+改寫 import,相對資源與快取行為都很彆扭。現況改成:專用 Service Worker(sw-canvas.js,scope 在 /playgrounds/,只攔截 /playgrounds/canvas/)在記憶體裡依遊樂場 postMessage 過來的快照回 Response。HTML 回應帶一點 CSP(base-uri/object-src/frame-ancestors 等)。遊樂場的離線 SW 不快取 canvas 路徑,避免跟這條虛擬站台搶。
信任模型也寫死:這是本機實驗場,畫布與遊樂場介面同源,專案腳本碰得到同 origin 儲存與 parent。不做多租戶硬隔離;遊樂場也不放秘密。
OPFS 當專案權威
每個專案一個 id,檔案進 Origin Private File System。重點:
- 文字/二進位都可存;編輯器只開判為文字的檔。
- 匯入/匯出專案:副檔名
.sam(ZIP 容器,專案包裹;只接受.sam);binary 原樣。可自 GitHub public repo 拉檔寫入(之後與遠端無關)。 - 不支援 OPFS 的瀏覽器直接失敗,不默默退回 localStorage 假裝同一套儲存。
另外還有幾塊也落在 OPFS、但跟「專案原始碼樹」分開:模擬 KV、checkpoint、仿 D1(env.DB)、Secrets 各有自己的前綴目錄(執行期狀態/side store)。匯出、匯入、複製專案時,預設只搬原始碼;要一併帶走 KV/DB/Secrets 得顯式勾選(.sam 裡可選 .playgrounds-state/)。預設不帶,是為了避免 session 或密鑰當原始碼一起拷走。
functions.js:Workers 形的 /api/*
畫布裡的 fetch("/api/…") 會被橋到 /playgrounds/canvas/<projectId>/api/…,由遊樂場介面執行該專案根目錄的 functions.js:
export default {
async fetch(request, env, ctx) {
// …
},
};
形狀對齊 Cloudflare Workers 的直覺:env 可注入模擬 KV(Durable:寫 OPFS)、以及(在現行 Agent 專案)env.HOST;工具 session 期間則是窄一點的 env.TOOL。另有仿 D1(env.DB)(sql.js 子集)與 Secrets(遊樂場 UI 寫入;HOST 只能列名、讀不到值)。沒有 functions.js 時 API 回 503。
這條路的用意是:靜態前端+一點 serverless 形後端,可以在分頁裡驗證;真要上線再部署到真正的 edge,而不是把沙盒當成正式託管。
Agent:也是一種單頁小程式
沙盒主軸還是開發 SAM。若把某個專案設為現行 Agent,遊樂場才維持(至少)兩條執行面:
- 工作專案:編輯器+原畫布——平常在改的那份單頁小程式。
- 現行 Agent:另一個 SAM 專案,UI 跑在左側 Agent 區(與 Files 用 Tab 切換;同樣走 canvas SW/
index.html),不是遊樂場介面手寫的 chat 元件。
所以 Agent 不是沙盒外掛的「第二套產品」,而是剛好需要 env.HOST 的那種 SAM。內建範本偏向改檔→重載→讀 console;若要做特定功能助手,也是再寫/複製一個 SAM,改 prompt 與工具編排,再設為現行 Agent——不必只能當 coding agent。
Agent 跟環境互動的正式通道只有一條:
Agent UI → fetch("/api/…") → 該專案 functions.js → env.HOST
HOST 提供版本化能力(apiVersion/capabilities):列檔、讀寫(可選 expectedHash)、search、重載畫布、讀/等 console、checkpoint、建立/複製/開啟專案、非互動 runCmd、掛載/關閉工具等。約束包括:
- 只有現行 Agent 專案才注入
HOST; - 禁止經 HOST 寫入現行 Agent 自己的檔案(避免熱改執行中的自己);
- 刪專案:HOST 只刪帶
agentManaged標記、由 agent create/clone 出來的專案。
LLM 走 BYOK:endpoint/model/key 留在 Agent 專案 UI 的儲存裡,本站不代打、不存 key。
工具:第三個角色,掛在編輯器槽
WASI/runCmd 適合短跑、非互動的 CLI;長時間互動的 viewer/editor 需要 Web UI。站上 /tools/ 是作者代選、少而穩的公開工具箱,沒辦法適配每人任務——所以沙盒多了一條路:把另一個 SAM 以「工具」角色掛進 Editor 槽(取代預設的 CodeMirror/媒體預覽),工作專案 activeId、檔案樹、工作畫布都不跟著切走。
三角色可以並存:
| 角色 | 跑哪裡 | 拿到什麼 |
|---|---|---|
| 工作專案 | 編輯器預設/原畫布 | 平常在改的那份 |
| 工具專案 | Editor 槽(第三條 canvas 管線) | 窄 **env.TOOL**+遊樂場介面核發的 grant(host 專案+paths+read|readwrite) |
| 現行 Agent | 左側 Agent 區 | 完整 env.HOST(含 openTool) |
重點是 TOOL ≠ HOST:工具拿不到專案列表刪除、setActiveAgent、整棵 FS 這類權力;關閉工具 session 就卸權。同一專案可在不同場合換角色——先當工作物改完,之後再當工具掛上。個人可復用的那些 SAM 留在 OPFS 裡長成「個人工具箱」,不進 /tools/ 登錄表;站上工具箱也因此不再擴張新頁。
下方面板:Console、Python、JavaScript、Shell
| 面板 | 實作要點 |
|---|---|
| Console | 畫布 runtime 的 log/error,遊樂場介面轉送進 buffer;agent 也可經 HOST 讀 |
| Python | 隔離 Pyodide Worker(CDN 釘版);人類 xterm REPL 與 HOST.runPython 共用同一 Worker;%pip 允許清單、%run 可把專案 .py 同步進 Pyodide FS |
| JavaScript | 另一個 Worker 沙盒(與畫布 runtime 分開);REPL+%run 專案 .js(含相對 ESM);無 npm |
| Shell | 仿 Linux 命令列(xterm+WASI preview1 調度器;瀏覽器內建 Wasm);FS=工作專案 OPFS;允許清單 CLI(如 jq、uutils coreutils 子集);可簡易管線;無外網/sockets |
Agent 側對應的是非互動 HOST.runCmd:與人類 Shell 共用 runner/佇列/OPFS 語意,但不共用字元級 TTY,也不吃管線字串。
曾經試過在下方塞 v86/Alpine 當「真 Linux」。對「輕量 Web+functions」主軸槓桿低(慢、無網、無編譯鏈),後來整段拿掉;人類路徑改成 REPL,命令列則走薄一點的 WASI,也不做互動式 HOST.shell。
邊界(技術上不做的)
- 不當部署環境、不當完整 Node/WebContainer。
- 不做通用 CORS proxy/站內代抓任意外網。
- 不承諾模擬 KV/DB 與雲端 Cloudflare 全相容。
- 不做真 Linux VM/互動式 agent TTY;Shell 是 WASI 允許清單,不是 Bash guest。
- 不把 Tool SAM 登錄進站上
/tools/,也不給工具 iframe 完整HOST。 - 頁面
noindex,也不進 XML sitemap——它是站內實驗頁,不是要靠搜尋引擎養流量的薄頁。
結構可以再拆細講(畫布 SW 協定、HOST/TOOL 錯誤碼、Pyodide 與專案 FS 同步)。這篇先把骨架放在桌上;要按的從沙盒進去即可。