預覽
預覽會把具有網頁介面的東西放到公開連結上:在中繼所在機器的某個連接埠上監聽的 dev server,或是一個存放一般檔案的資料夾。你可以從任何裝置開啟這個連結,也可以把它傳給任何人。中繼本身不會因此暴露:預覽由另一個監聽器在另一個主機名稱上提供。
常見用途:
- 在手機上開啟 App。 agent 啟動 dev server,你點一下連結,就能在實際裝置上試用版面。
- 把連結傳給別人。 同事不需要存取你的機器或中繼,就能開啟設計稿或建置好的網站。
- 讓 webhook 指向 dev server。 需要公開位址的服務可以呼叫你正在開發的 App。
本頁是給擁有者和管理者的指南。agent 的工具及其參數、結果與限制,請見 preview。
預覽連結是公開的
連結中的 token(128 位元)是唯一的保護。任何拿到連結的人都不必登入就能連到這個 App。為含有真實資料的東西建立預覽,就是真正的暴露。用完後請關閉預覽。
預覽如何與中繼隔開 #
預覽由第二個 HTTP 監聽器在它自己的連接埠上提供,並發布在它專屬的主機名稱上。這帶來兩點:
- 中繼不在那個連接埠上。 中繼的 API、網頁 App、即時連線和分享連結都沒有掛載在那裡。預覽流量無法觸及它們,因為它們在那個監聽器上根本不存在。建立或關閉預覽需要中繼的存取 token,而且只能在中繼自己的連接埠上進行。
- 主機名稱不同。 被預覽的 App 和中繼在不同的瀏覽器來源中執行。它的 JavaScript 無法送出你的中繼登入 cookie,也無法讀取中繼網頁 App 的儲存空間。
必須是主機名稱不同,而不只是連接埠不同。cookie 會忽略連接埠,所以 relay. 會共用 relay. 的登入 cookie。主機名稱相同時,中繼會拒絕啟動預覽通道。
此外,預覽通道絕不會把中繼的登入 cookie(copilot_)或含有中繼存取 token 的 Authorization 標頭轉送給被預覽的 App,即使手動建構的請求帶有它們也一樣。App 自己的 Authorization 標頭則會照常通過。
不過,預覽之間共用同一個主機名稱,各自位於自己的路徑下。見注意事項。
設定 #
在管理者設定之前,預覽是關閉的。共有三個步驟。
1. 主機名稱 #
選一個你能控制、名稱與中繼不同的主機名稱,例如在 relay. 旁邊用 preview.。把它的 DNS 指向你的前端 proxy 或 tunnel。預覽是以路徑而不是子網域區分,所以在你已有的 DNS 區域下用一個單層的主機名稱就夠了。在 Cloudflare 上,這個名稱由 Universal 憑證涵蓋。像 *. 這種兩層的萬用字元則不在涵蓋範圍內,這也是為什麼不為每個預覽分配子網域。
2. 前端 proxy #
proxy 會把預覽主機名稱送到預覽監聽器,監聽器預設使用中繼的連接埠加一(中繼使用 3333 時為 3334)。請用 previews. 固定一個連接埠;proxy 需要穩定的目標。
Caddy
preview.example.com {
reverse_proxy 127.0.0.1:3334
}
Cloudflare Tunnel
在你已經使用的 tunnel 上新增第二個公開主機名稱,位置在 Cloudflare 儀表板的 Public Hostnames → Add(公用主機名稱 → 新增)下,或在 tunnel 的 config. 中:
ingress:
- hostname: preview.example.com
service: http://127.0.0.1:3334
- hostname: relay.example.com
service: http://127.0.0.1:3333
- service: http_status:404
3. previews 區塊 #
把這個區塊加到中繼的 config.,然後重新啟動中繼(見 oar restart):
{
"publicHostnames": ["relay.example.com"],
"previews": {
"enabled": true,
"port": 3334,
"bindHost": "127.0.0.1",
"publicBaseUrl": "https://preview.example.com",
"allowedTargetHosts": [],
"maxLive": 8
}
}
| 設定鍵 | 要填什麼 |
|---|---|
public |
中繼本身回應的每一個主機名稱。對 public 的檢查會使用這份清單(並一律包含 localhost 和 127.),所以你漏掉的主機名稱不會被檢查。 |
previews. |
設為 true 以開啟預覽通道。預設為關閉。 |
previews. |
監聽器的連接埠。預設:中繼的連接埠加一。0 會在每次啟動時挑一個空閒的連接埠,前端 proxy 無法跟著變動。 |
previews. |
監聽器綁定的位址。預設為 127.。請保持在迴路位址上:前端 proxy 在同一台機器上執行。 |
previews. |
只有在 bind 不是迴路位址時才需要。此時每個預覽都能不經 proxy 從網路上連到。 |
previews. |
預覽通道的公開位址:https://。連結會以它建構。 |
previews. |
除了這台機器之外,連接埠預覽可以指向的主機,例如容器或 VM。 |
previews. |
整個中繼同時存在的預覽數上限。預設為 8。 |
所有設定鍵、預設值與邊界情況,都列在 config.json 的表格中。
環境變數覆寫 #
COPILOT_、COPILOT_、COPILOT_、COPILOT_ 和 COPILOT_ 會覆寫對應的設定鍵。它們的值以及誰優先於誰,請見環境變數。
預覽通道拒絕啟動時 #
設定錯誤的預覽通道絕不會讓中繼停擺。中繼會繼續執行,而預覽通道保持關閉。這些檢查沒有一項會降級為警告,因為每一項都在保護上述的隔離。
| 原因 | 訊息 |
|---|---|
public 缺少,或不是 http(s) 位址 |
previews. |
public 使用了中繼的主機名稱 |
previews. |
| 連接埠低於 1024 | previews. |
| 連接埠是中繼的連接埠或它的 CLI 連接埠 | previews. |
| 無法算出連接埠 | previews. |
bind 不是迴路位址 |
previews. |
你會在兩個地方看到:
- 中繼記錄,在啟動時:每一項未通過的檢查會有一行
PREVIEW LANE disabled: <message>。檢查通過時,記錄會顯示[preview] listener on http://。記錄的位置:檔案與資料夾。127. 0. 0. 1: 3334 → https:// preview. example. com - 發布請求的回應。 API 會回應 503,附上
Preview lane failed its startup checks,並在details中列出訊息。agent 的preview工具會轉達這個回應,而 agent 會被告知要轉告你。未設定previews.時,回應為enabled Preview lane is disabled (set previews.,記錄中也不會有任何一行。enabled and previews. public Base Url)
Settings → Previews(設定 → 預覽)和 / 的通知都不會顯示原因。設定分頁會列出現有的預覽,通知只說發布失敗(見 /)。請查看中繼記錄。
如果連接埠被其他程式佔用,檢查會通過,但監聽器無法綁定。記錄會顯示 PREVIEW LANE failed to bind :<port> — <reason>。就 API 而言預覽通道已啟動,但預覽連接埠上沒有任何東西回應,所以連結打不開。請釋放該連接埠,或設定另一個 previews.。
三種發布方式 #
請 agent 發布 #
說出你要什麼:「啟動 dev server 並給我一個連結」、「讓我看看建置好的網站」。agent 會使用 preview 工具;在沒有這個工具的供應商上,則使用中繼的預覽 API。它會告訴你連結,並說明連結是公開的。哪些供應商有這個工具,請見參考頁面上的表格。
輸入區中的 /preview #
這個指令在你的瀏覽器中對中繼執行。它適用於所有供應商,而且不耗用 agent 的回合。只有在訊息沒有附加任何東西時才會執行。通知會出現在對話中。
| 你輸入 | 會發生什麼 |
|---|---|
/ |
發布連接埠 5173,標籤為「web app」。顯示 Preview published: <url>。 |
/ |
把資料夾 ./ 發布為靜態預覽,標籤為「built site」。第一個字是資料夾,所以它不能包含空格。顯示 Preview published: <url>。 |
/ |
列出中繼的每一個預覽,每行一個:token 的前 8 個字元、標籤、連接埠或資料夾、離線時加上 — offline,以及連結。沒有預覽時顯示 No live previews.,預覽通道關閉時顯示 The preview lane is disabled on this relay.。 |
/ |
關閉這個對話的預覽。沒有預覽時顯示 No live previews in this conversation.。有多個時,會列出符合的預覽並要求你輸入更長的前綴。 |
/ |
關閉 token 以這 4 到 32 個十六進位字元開頭的預覽,不限對話。顯示 Preview closed: <label>。沒有符合時:No preview matches "3f9c". |
/ |
顯示用法說明行;任何它無法解讀的輸入也一樣。 |
由數字組成的第一個字一律視為連接埠,超出 1 到 65535 的連接埠會顯示用法說明行。若資料夾名為 8080,請輸入 ./。
如果發布失敗,不論原因為何(連接埠錯誤、資料夾在工作資料夾之外、預覽通道已滿、預覽通道已停用),通知都相同:
Preview failed — check the port/path, or whether the preview lane is enabled (Settings → Live previews).
通知不會顯示確切原因。清單在 Settings → Previews 中,位於 Live previews(即時預覽)標題下。若要查看原因,請呼叫 API(見下文),或查看疑難排解中的表格。如果無法連到中繼,通知為 Preview command failed — could not reach the relay.。指令選單及其按鍵說明請見輸入區。
API #
以上所有方式都使用中繼連接埠上的同一個 API。它需要以中繼的存取 token(config. 中的 auth,也就是你登入時用的那個 token)作為 bearer token。請先註冊,以便知道基礎路徑,再在該路徑下啟動 dev server:
curl -sX POST http://127.0.0.1:3333/api/previews \
-H "authorization: Bearer $RELAY_TOKEN" -H 'content-type: application/json' \
-d '{"conversationId":"<conversation id>","port":5173,"label":"web app"}'
{
"ok": true,
"url": "https://preview.example.com/test_0123456789abcdef0123456789abcdef/",
"basePath": "/test_0123456789abcdef0123456789abcdef/",
"preview": { "token": "0123456789abcdef0123456789abcdef" },
"existing": false
}
當對話已經有同一個連接埠或資料夾的即時預覽,而你取回的是那一個時,existing 為 true。
| 請求 | 用途 |
|---|---|
POST / |
port 或 dir(擇一),可選填 conversation、host、label。 |
GET / |
所有預覽。?conversation,或 session worker 送出的對話標頭,會把範圍縮小到單一對話。 |
DELETE / |
關閉一個預覽。dev server 不受影響。0.9.14 新增 指定了對話的請求(方式同 GET)只能關閉該對話的預覽;關閉其他對話的預覽會被拒絕(403)。 |
讓 App 在預覽的路徑下運作 #
每個預覽都位於共用預覽主機名稱上的 / 之下。由 proxy 的行為可以歸納出這條規則:
- 瀏覽器請求
/。test_<token>/ … - App 收到的路徑不含這個前綴。
- 所以 App 在頁面中放的連結和資源網址,必須帶有前綴或是相對網址,而且它仍必須回應不帶前綴的請求。
proxy 在過程中會做這些事:
| proxy 會 | |
|---|---|
| 移除前綴 | App 收到 / 之後的路徑,連同查詢字串。造訪不帶結尾斜線的 / 會被重新導向(301)到加上結尾斜線的同一位址,讓相對網址在預覽內解析。 |
送出 X- |
不含結尾斜線的前綴。 |
送出 X- |
預覽監聽器收到的 Host。 |
送出 X- |
前端 proxy 送來的值;若它沒有送,則為 https。 |
送出 X- |
連線到監聽器那一方的位址。在同一台機器上的前端 proxy 後面,那就是 proxy。 |
設定 Host |
設為 App 自己的位址(127.),供會檢查它的 dev server 使用。 |
改寫 Location |
重新導向到 / 會變成 /,重新導向到 App 自己位址的絕對網址也一樣。已帶有前綴的重新導向、以 // 開頭的、相對的,或指向其他網站的重新導向則保持不變。 |
限定 Set- 範圍 |
cookie 會加上 Path=/ 並移除 Domain,因此一個預覽的 cookie 不會傳到另一個預覽。名為 copilot_ 的 cookie 會被丟棄。 |
| 標記回應 | 加上 X-,並移除 Service-。 |
它不會改寫頁面、樣式表或腳本的內容。在 HTML 中寫入 / 的 App 會向預覽主機名稱的根目錄請求,而那裡沒有任何預覽,瀏覽器會得到 No such preview 頁面(404)。這就表示 App 不知道自己的前綴。
可行的做法 #
- 相對網址。 頁面使用
./這類位址的 App 可以直接運作:頁面位於assets/ app. js /,所以這些位址會在預覽內解析。這是從 proxy 的運作方式推得的,並未逐一在各框架上測試。test_<token>/ - 讀取前綴。 以不帶前綴的路徑做路由、並從
X-建構連結的 App,完全符合這條規則。Forwarded- Prefix - 建置好的網站。 以相對的基礎路徑或等於前綴的基礎路徑建置,再把輸出資料夾發布為靜態預覽。對
/的請求會由該資料夾提供,所以兩種網址都能運作。test_<token>/ assets/ …
無法直接使用的做法 #
讓 App 只回應帶有前綴之請求的工具設定並不適用,因為 proxy 會把前綴移除。
- 把
base設為前綴(--base)的 Vite dev server。 已對照中繼所在機器上的 Vite 8.3.1 原始碼確認:dev server 只處理以base開頭的路徑。它對/的請求回應重新導向到base,而 proxy 不會改動這個重新導向,所以瀏覽器會陷入迴圈。其他不帶前綴的請求會得到 404,說明伺服器有公開的基礎網址。熱重載只在base下的路徑監聽,而被移除前綴的請求不符合這個路徑。請改用建置網站的做法,或改用從根目錄提供內容並使用相對網址的 Vite 設定。 - 把 router 掛載在前綴上的 Express(
app.)。App 收到的是use(prefix, router) /,而不是x /,所以掛載點不會符合。請把 router 掛載在test_<token>/ x /,並從X-建構連結。Forwarded- Prefix
Next.js(base、asset)和 SvelteKit(kit.)在 OAR 給 agent 的說明中有提到,但這些說明並未對照 proxy 的實際行為檢查過,而且這兩個工具在這裡都無法測試。如果它們的基礎路徑設定讓 App 預期每個請求都帶有前綴,那麼它基於與 Vite 相同的原因並不適用。未經驗證:依賴它之前請先試試看。
App 需要前綴時,請先發布
agent 會在啟動 dev server 之前呼叫 create,這樣如果 App 需要前綴,就能把前綴提供給它。對於使用相對網址就能運作的 App,順序並不重要。
靜態預覽 #
用 dir 取代 port 時,中繼會自己提供一個資料夾的內容。不需要執行任何東西,適合建置輸出、設計稿、單一 HTML 檔案或覆蓋率報告。
- 資料夾從對話的工作資料夾取得(絕對路徑,或相對於工作資料夾的路徑),而且必須位於工作資料夾內。未指定對話而發布的預覽,會使用中繼自己的工作資料夾。這會在你發布時以及每次請求時檢查,所以事後建立的符號連結也無法通往外部。
- 資料夾路徑會提供
index.。不做任何快取。html - 永不提供:名稱以點開頭的檔案和資料夾(
.、git .),以及金鑰檔案(env *.、pem *.、名稱以key id_、rsa id_、ed25519 id_或ecdsa id_開頭的檔案)。dsa - 相對的資源路徑可以運作,以預覽前綴開頭的路徑也可以。以根目錄起始的絕對路徑(
/)則不行。assets/ app. js - 沒有
index.的資料夾,以及任何不存在的檔案,都會得到html Not found頁面(404)。 - 靜態預覽一律為
online,並拒絕 WebSocket 連線。
管理預覽 #
- 背景任務面板中的卡片。 對話的每個預覽都有一張卡片,顯示它的標籤、連接埠、連結為公開的提醒、中繼檢查過後的
online或offline徽章,以及 Open(開啟)、Copy link(複製連結)和 Close(關閉)按鈕。這個面板在背景任務中有說明。 - 對話紀錄中的連結卡片。 當預覽在對話的回合執行期間發布時,回覆會附上一張連結卡片,不論是 agent 還是你用
/發布的。在回合之間發布的預覽沒有對話紀錄卡片。預覽消失後,卡片會顯示preview closed徽章,且不再有連結。分享的對話只顯示卡片的標籤,而文字中引用的每個預覽連結都會顯示為[preview link removed]。 - Settings → Previews。Live previews 清單會顯示中繼的每一個預覽,涵蓋所有對話,並附上對話 id 的前 8 個字元,以及 0.9.11 新增 發布時間和上次開啟時間。當你已離開某個預覽所在的對話時,可以在這裡關閉它。見 Settings(設定)。
offline 和 closed 是不同的。中繼每 15 秒會檢查一次每個連接埠預覽,方法是嘗試連線到該連接埠(發布時也會立即檢查一次)。沒有任何東西回應時,卡片會顯示 offline 徽章和提示「offline — dev server not responding」。連結仍然有效:在同一個連接埠上重新啟動伺服器,同一個連結就能用。closed 表示預覽已經消失,連結不再能解析。
關閉預覽只會結束連結。dev server 會以原本的背景任務身分繼續執行。
預覽不會自行過期。它會在你關閉它時結束,或 0.9.11 新增 在你刪除它的對話時結束(包括透過重建中繼的快取)。封存對話會讓它的預覽保持運作,並仍列在 Settings → Previews 中。
在同一個對話中再次發布同一個連接埠或資料夾,會傳回已經在運作的那個預覽,連結相同、標籤不變,而不會建立新的預覽。
整個中繼同時最多只能有 8 個運作中的預覽(previews.)。拒絕訊息會列出最舊的三個,附上各自的發布時間和上次開啟時間,以及關閉它們的方法。
中繼重新啟動之後 0.9.11 新增 #
預覽在中繼重新啟動、更新或當機後都會保留:中繼把它們存在資料庫中,並在啟動時還原,所以你發出去的連結仍然可用。每個預覽都會先重新檢查,若檢查發現它已不再有效就會結束:
- 資料夾預覽,其資料夾已不存在,或已不在其對話的工作資料夾內;
- 機器重新開機後的連接埠預覽:dev server 已經不在,而該連接埠之後可能屬於別的程式;
- 對話已不存在的預覽。
Linux 和 macOS 能可靠地回報重新開機。在 Windows 上,Restart(重新啟動)會算作一次重新開機,但在 fast startup(快速啟動)開啟時(Windows 預設值),Shut down(關機)後再開機則不算:之前的連接埠預覽會回來,並連到當時在該連接埠上監聽的任何程式。不再需要的預覽請關閉。
無法得出結果的檢查——因為資料庫忙碌,或資料夾因為已不存在以外的原因無法讀取——不會結束預覽。不過這段期間它不會被提供:Settings → Previews 和工具的 list 會把它顯示為未提供(unverified),它會計入上限,也可以被關閉。中繼每 15 秒會重新檢查一次,檢查一有結果,就會在原本的連結下重新提供它;在它的對話中再次發布同一個連接埠或資料夾,會立即做到這一點。10 分鐘都沒有結果,預覽就會結束,記錄中也會註明。
連接埠預覽在啟動後也會立即探測一次,讓它的 online 或 offline 徽章保持最新。中繼的記錄以簡短雜湊值稱呼預覽,絕不使用它的 token。另見重新啟動會保留什麼、會失去什麼。
WebSocket #
連接埠預覽會在同一個前綴下轉送 WebSocket 連線,並像任何請求一樣移除前綴,所以當瀏覽器透過預覽連結連線時,App 自己的 socket 可以運作。(熱重載是 App 自己的事:見上面關於 Vite 的說明。)靜態預覽,以及未知或已關閉的連結,會以 404 回應 WebSocket 請求。如果 App 以一般回應來回應升級請求,瀏覽器會得到那個狀態碼。如果 App 停止運作,則為 502。
注意事項 #
- 預覽之間共用同一個來源。 cookie 限定在各預覽的路徑內,token 也無法猜測,但兩個預覽彼此之間的隔離,不如各自與中繼之間的隔離那麼徹底。
- 預設只限你自己的機器。 連接埠預覽只能指向迴路位址,再加上
previews.中的主機。中繼自己的連接埠一律會被拒絕,因為發布其中一個會把中繼的登入頁面放到公開主機名稱上。allowed Target Hosts - 中繼不會執行 App。 它從不啟動或重新啟動 dev server。如果伺服器停止,卡片會變成
offline。
依供應商 #
預覽在每個供應商上看起來都一樣;只有 agent 發布的方式不同,如參考頁面所示。/ 和 Settings → Previews 適用於所有供應商。
疑難排解 #
| 你看到的情況 | 原因與解決方法 |
|---|---|
agent 或 API 回應 Preview lane is disabled 或 Preview lane failed its startup checks(503) |
預覽通道已關閉或拒絕啟動。閱讀中繼記錄中的 PREVIEW LANE disabled 行,修正 previews,然後重新啟動中繼。見預覽通道拒絕啟動時。 |
連結顯示 Preview upstream unreachable 頁面(502) |
沒有任何東西在該連接埠上監聽,或連線失敗。在同一個連接埠上重新啟動 dev server 並重新載入;連結仍然有效。 |
資源缺失或頁面空白,且對 / 的請求得到 No such preview(404) |
App 寫入了以根目錄起始的絕對路徑。讓它輸出相對網址或帶前綴的網址(規則)。 |
| 頁面不斷循環載入,或 App 回應 404 並提到「public base URL」 | App 預期每個請求都帶有前綴,但 proxy 會把它移除(無法直接使用的做法)。 |
整個連結都顯示 No such preview |
連結不明或已關閉、它的對話已被刪除,或重新啟動時發現它已不再有效(重新啟動之後)。請重新發布。 |
預覽顯示為未提供(unverified) |
中繼在啟動時無法檢查它。它每 15 秒會重新檢查一次;在它的對話中再次發布同一個目標,會立即讓它恢復。 |
| 連結完全打不開 | 前端 proxy 或 DNS 尚未設定,或監聽器無法綁定它的連接埠(記錄中的 PREVIEW LANE failed to bind)。 |
卡片顯示 offline |
dev server 已停止回應。在同一個連接埠上重新啟動它。 |
Too many live previews (max 8); previews stay until closed. Oldest: …(429) |
在 Settings → Previews 中、用 /,或用 DELETE / 關閉一個;agent 可以用工具的 close 動作關閉自己對話的預覽;或調高 previews.。 |
Port <n> belongs to the relay and cannot be previewed(400) |
你給的是中繼的連接埠、它的 CLI 連接埠或預覽連接埠。請發布 dev server 的連接埠。 |
Target port must be between 1024 and 65535(400) |
使用更高的連接埠。 |
Target host ... is not loopback and not in previews.(400) |
把該主機加到 previews.。 |
dir must live inside the workspace root (...、Directory not found: ... 或 Not a directory: ...(400) |
資料夾必須存在,且位於對話的工作資料夾內。 |