箱子狀態
箱子什麼時候凍結、喚醒、永久停掉,什麼算使用、什麼讓箱子保持醒著,以及 app 怎麼顯示在用箱子的對話還在不在做。
claimed → ready → takeover(可選)→ stopping → terminated
↕
frozen
閒置規則,sandbox_start 的說明原文(英文)如下:
An idle box is frozen after 10 minutes without a call, with /work kept; the next call thaws it. A frozen box is stopped for good 24 hours after its last use. idleTimeoutMin sets your own limit instead of these: the box stops for good after that many minutes without a call, frozen or not. It is capped at 10080 minutes (7 days). An attached phone freezes and thaws with its box (an iPhone restarts its apps); while frozen it holds no device slot and is not billed. A pending sandbox_review keeps neither the box nor its phone awake: they freeze and are stopped by these same rules, and opening the review wakes a frozen box. Commands started with background: true (a dev server, a long build) count as activity for up to 1 hour after your last call; after that a box freezes anyway (they carry on when the next call thaws it). Requests through the box's URLs count as activity for up to 2 hours after the last call; after that the box freezes, and the next page load in a browser wakes it again (a few seconds). Background fetches from an open tab don't wake it. With no room on any host it fails with "no box capacity right now" (HTTP 503 over REST); waitForCapacitySec waits for room instead.
細節:
- 使用:這一頁說的「使用」(例如「最後一次使用」)是指會動到箱子的工具呼叫與喚醒。會動到箱子的工具呼叫是
sandbox_start、sandbox_exec、sandbox_sync、sandbox_get(sandbox_pull也是)、sandbox_shot(用urls一次拍好幾頁的除外)、sandbox_scene、sandbox_wire、sandbox_procs、指定箱子的sandbox_secrets與sandbox_build;有人接手箱子(連上接手畫面、在上面打字與點擊),以及別的箱子經 link 連進來,也算。只看狀態或只留話的sandbox_status、sandbox_list、sandbox_say、sandbox_feedback不算;sandbox_review、sandbox_device、sandbox_takeover與用urls的sandbox_shot也不算,只是它們碰到凍結的箱子時會先把它喚醒(list的sandbox_device除外),那次喚醒就算。最後一次使用是這些呼叫最後一次開始或結束、或最後一次喚醒的時間,取最晚的,也就是sandbox_status與sandbox_list回的lastUsedAt。會動到箱子的呼叫一開始就算使用,所以還在跑的sandbox_sync、sandbox_get不會被凍在半路(前景的sandbox_exec跑多久箱子就醒多久);任何呼叫碰到正在凍結的箱子,會等凍結做完的那幾秒,再把它喚醒接著跑,不會失敗。 - 箱子 10 分鐘沒有活動就會變成
frozen:記憶體存成快照、/work保留、不再計箱子時間。活動是使用(上一項),或在最後一次使用後 2 小時內、經箱子網址進來的請求(每個 HTTP 請求都算,WebSocket 在建立連線時算一次),所以有人在用webUrl,箱子在這段時間內就不會凍結;app 的即時畫面與箱子清單上的即時縮圖不算(兩者都是有人在看時即時傳來的,不會保存,跟點數講的箱子畫面不同)。過了這 2 小時,網址上的請求就不再讓箱子醒著:還在用頁面的人可能會在使用中遇到箱子凍結,他下一次載入頁面就會把它喚醒(見下面)。有人接手中、有前景命令在跑、或有醒著的箱子經 link 連著它時,箱子都不會凍結;正在錄影時也不會,但只到從最後一次使用起算滿 1 小時為止;過了之後,等到這裡列的其他東西都沒在讓它醒著,錄影就會被停掉、不上傳(見sandbox_shot)。用background: true起的命令(dev server、很久的 build)只在最後一次使用後 1 小時內讓箱子保持醒著;過了之後箱子照樣凍結,那個程式在下一次呼叫解凍後接著跑。sandbox_status的freeze講:at是情況不變的話箱子會凍結的時間,blockedBy[]是現在撐著它的東西(background、url、recording附各自算到的until;takeover、foreground、link、phone要等它們結束,這時沒有at)。閒置早就超過 10 分鐘還是ready的箱子,就是被其中一樣撐著。 - 凍結的箱子幾秒內就能喚醒(接著手機時要等手機先接回來,約一分鐘):指定這個箱子的工具呼叫(
sandbox_status、sandbox_say、sandbox_feedback、sandbox_stop與list的sandbox_device除外)、別的箱子經 link 連進來、有人在瀏覽器載入它的頁面(下一項)、登入這個帳號的人在 app 的箱子頁面按「看畫面」(箱子凍結時螢幕上有東西,頁面才有這個按鈕:見哪個對話在用箱子的posterUrl),或帶 OAuth access token 或 API key 呼叫POST /v1/boxes/{id}/wake。喚醒算一次使用。喚醒要帳號還有點數(不然回 402)。sandbox_stop對凍結的箱子也有效。什麼都不會丟。 - 在瀏覽器載入凍結箱子的頁面就會喚醒它:
Sec-Fetch-Mode: navigate的GET或HEAD(輸入網址、點連結、重新整理),或沒送Sec-Fetch-Mode的瀏覽器發出、Accept含text/html的請求。edge 請 control 把箱子解凍,先回一個小頁面「Waking this box… This page reloads by itself in a few seconds.」(503、Retry-After: 3、Cache-Control: no-store);頁面會自己一直重新整理到 app 出現,全部大約 10 秒。這次喚醒算一次使用。同一個箱子每 15 秒最多送一次喚醒請求,這段時間裡的頁面載入拿到的是同一頁。- 經網址的其他請求都不會喚醒它:開著的頁面發出的
fetch與 XHR、圖片與 script、WebSocket 與POST都拿到 503{"error":"box is frozen","status":"frozen"}。開著沒關、一直輪詢的分頁既叫不醒箱子,也不能讓它醒著超過上面的 2 小時。 - 帳號沒有點數時,頁面顯示「This box is paused」(402),也不會自己重新整理:在 app 加值之後再重新整理。
- 停掉的箱子不會這樣回來:它的網址回 503
box is terminated,新的箱子有新的網址。
- 經網址的其他請求都不會喚醒它:開著的頁面發出的
- 箱子的網址凍結前後都一樣;只有新的箱子才有新的網址。
- 解凍後第一次啟動大型程式會比平常慢,常常慢好幾倍(3 秒開好的 Electron App 可能要 12 秒):它的檔案已經不在箱子的記憶體快取裡。要量或錄冷啟動之前,先開一次暖機。
- 解凍後,boxd 會關掉凍結前就開著、連到環境位址與 link 的連線(解凍後約 15 秒內發現);解凍之後才開的連線不動。程式要重新連線:資料庫連線池通常會自己重連,長時間連著的用戶端要自己重試。
- 凍結的箱子在最後一次使用後 24 小時永久停掉(
/work跟著消失);凍結的時間比最後一次使用晚的話,從凍結時算起,所以因點數用完而凍結的箱子會留滿 24 小時。用sandbox_review交件、在「待驗收」等人的箱子也照同樣的規則凍結、停掉:等驗收不會讓任何東西保持醒著。凍結時人打開交件會把它叫醒;已經停掉的,交件就結束,要在新的箱子重新交件。箱子停掉後仍會在 app「已停止(7 天內)」清單顯示,拍攝後未滿 7 天的截圖與錄影也可查看。有idleTimeoutMin的箱子則改成多少分鐘沒有使用就停掉,凍結中的也一樣;載入頁面喚醒它,跟其他喚醒一樣會重新開始算。 sandbox_status與sandbox_list的stopsAt,是沒有人再用它的話這些規則永久停掉箱子的時間(不會停掉時沒有這個欄位)。離stopsAt30 分鐘內,用過這個箱子、或指名它的對話裡,每個工具結果後面都會多一行,像box <id>: the idle rules stop it for good at <時間> (in 25 minutes) unless something uses it before then, and /work goes with it.;會動到箱子的呼叫會把stopsAt往後推,sandbox_status與sandbox_list不會。還要的東西在那之前拿出來。- 這些規則停掉的凍結箱子,凍結副本會再留 24 小時:
sandbox_status的restorableUntil(sandbox_list帶all: true也有)寫到什麼時候,指名這個已停箱子的呼叫也會說怎麼拿回來。sandbox_start { "restore": "<id>", "goal": "<它的 goal>" }把同一個箱子照凍結那一刻拿回來,/work、跑著的程式、id 與網址都一樣(sandbox_start);接著的手機不會留。用sandbox_stop停掉的箱子不留副本。 - 有人在試用箱子時,你什麼都不用做:他們用箱子的網址,在最後一次使用後 2 小時內都能讓箱子醒著。過了之後,就算他們還在用,箱子也會凍結;他們下一次載入頁面,大約 10 秒就會把它喚醒,這次喚醒也算一次使用,所以又有 2 小時(午餐回來也一樣)。想要自己的期限再設
idleTimeoutMin。要自己看場景以外的頁面,sandbox_shot { "id": "<id>", "target": "url", "url": "http://127.0.0.1:8082/" }會在箱子裡開一個新的 Chromium 並回傳截圖;用sandbox_exec跑curl可以拿到 HTML。 - 接著的手機跟著箱子凍結:手機只在箱子要凍的時候才凍,讓箱子醒著的東西也讓手機開著;手機凍好之後箱子才凍,凍著的手機不佔裝置格、不計費;恢復箱子的那次呼叫會把它開回來,用同一個
deviceId與serial接上(Android 模擬器從記憶體快照接著跑,iPhone 模擬器重新開機、App 與資料都在,App 會重新啟動)。那台裝置主機當下沒有空格時,手機照舊凍著,等有空格再接回(見sandbox_device)。它的 App 有box的sandbox_review在等人也一樣:等的期間手機照常跟著箱子凍結,人打開交件時跟著箱子開回來。
因這些規則停掉的箱子,statusReason 會寫明,例如 frozen and unused for … 或 unused for …, over idleTimeoutMin …。
有人在箱子的畫面上時(接手,或打開了 box 的驗收),sandbox_sync 與 sandbox_exec(readOnly 的除外)回 423,並說人從什麼時候開始用、為什麼(自己按接管、sandbox_takeover 請的、或在試你交的件)、最晚什麼時候還回來。sandbox_get、sandbox_procs、拍螢幕、視窗或網址的 sandbox_shot、sandbox_status 與 sandbox_say 照樣能用(舊映像的箱子 sandbox_get 與 sandbox_shot 也回 423,錯誤會講)。這段時間要繼續做,就開另一個箱子把程式同步過去。
任何狀態都可能變成 interrupted:它底下的主機不在了(被雲端收回,或故障)。sandbox_status 會給原因;對那個箱子的下一個工具呼叫也會回同樣的內容。起一個新箱子重跑。雲端預告後才收回的主機,上面的箱子會先凍結並存起來,下一個呼叫就在別台主機解凍,根本不算中斷。沒能整個存下來的箱子,/work 可能還是存到了,錯誤裡就會寫 Its /work was saved at <時間>:對這個中斷的箱子用 sandbox_get 或 sandbox_pull 拿 /work,會拿到那一份 work.tar.gz(不含 node_modules、快取與超過 64 MiB 的檔,那個時間之後的改動也沒有)。除此之外,被中斷的箱子上什麼都不會留下。
哪個對話在用箱子
stdio 轉接器(parallelsandbox-mcp 0.3.0 以上)會告訴 ParallelSandbox 它的對話還在不在:
- 啟動時取一個隨機 id,每次 MCP 與 REST 呼叫都放在
X-Psbx-Agentheader 送出。 - 第一次工具呼叫之後,每 60 秒送一次
POST /v1/agents/<id>/heartbeat,body 是{"client": "<MCP client 名稱>"}。 - 對話關掉時(stdin 結束,或收到 SIGTERM、SIGINT、SIGHUP)送
POST /v1/agents/<id>/leave。沒送就結束的對話(kill -9、筆電睡著)在最後一次心跳 3 分鐘後算不在了。leave 之後又來心跳,就當同一個 id 回來了。 - 其他什麼都不送:沒有提示詞,沒有對話內容。
箱子記得最後一個對它呼叫工具的 agent id;沒帶 header 的呼叫保留原本的。id 是英數、- 與 _,1 到 64 個字元:兩個端點對其他的回 400 agent id: letters, digits, - and _ only, at most 64,不合規則的 header 則當沒帶。直接呼叫 HTTP 端點或 REST API 的 client 也可以這樣做:自己送 X-Psbx-Agent,並帶 OAuth access token 或 API key 呼叫這兩個端點(都回 {"ok":true})。兩樣都沒送的 client,箱子的 AI 狀態只看閒置時間:永遠不會是 left。
GET /v1/boxes、sandbox_list 與 sandbox_status 給每個還活著的箱子(開機中、ready、有人接手、凍結)一個 agent 物件 { "state": "working", "idleSec": 42 };正在停、已停與 interrupted 的箱子沒有。sandbox_list 與 sandbox_status 裡,箱子最後一個 agent id 就是發問的對話時,還多一個 thisConversation: true。idleSec 是從箱子上次被使用(工具呼叫、喚醒、人操作它的螢幕、link 連線)或開起來到現在的秒數。state 依序判斷:
left:箱子最後的 agent 送了 leave,或超過 3 分鐘沒送心跳。idle:箱子 1 小時以上沒被使用,不管它的對話還開不開著。away:箱子凍結了,或超過凍結時間(10 分鐘)沒被使用,而對話還在。working:其他情況。
同一份清單也說明箱子螢幕上有什麼:
screenInUse:虛擬螢幕上有東西(瀏覽器、模擬器);只跑 build 或測試的箱子是 false。screenActive:箱子是ready、screenInUse是 true 時為 true,不看螢幕最近有沒有變化:AI 在跑測試、畫面停著的箱子也算。posterUrl:箱子凍結當下的螢幕畫面(預簽,1 小時有效),只有那時螢幕上有東西才給。只跑 build 或測試的箱子沒有,不會給一張黑色桌面。每次凍結會用新圖替換舊圖,只保留最新一張(點數)。箱子停掉之後,GET /v1/boxes?all=1裡照樣帶最新那張。
app 用這些欄位分類還活著的箱子:
- 「待驗收」:等著人的箱子,有
sandbox_review卡片或sandbox_takeover在等人。 - 「AI 測試中」:只放
screenActive是 true、agent.state是working、又沒在等人的箱子。 - 「全部沙盒」:還在執行的箱子與最近 7 天已停止的箱子,各標上「AI 在用」(
working)、「AI 在忙別的」(away)、「AI 停手了 · 對話已關閉」(left)或「AI 停手了 · 1 小時沒用」(idle)。AI 停手了、又沒在等人的箱子有「接著做」與「收掉」兩個按鈕(接手別的對話留下的箱子)。
「凍結中」不再是給人看的狀態:清單上凍結的箱子是「AI 在忙別的」或「AI 停手了」,箱子頁面寫「AI 沒在動這個箱子」,下一次工具呼叫就會解凍。
所有工具與主題都列在工具參考。