sandbox:// 交接卡
Agent 在 sandbox 裡開了一個瀏覽器工作階段、產出了一份檔案、或整理出一整個資料夾時,需要一個
方式把使用者交接過去。做法是讓附件卡片的 defaultAction 指向 sandbox:// 這個自訂 scheme。
SDK 會在走到「開新分頁」的預設行為之前攔截它,解析成一個具型別的 intent,再交給對應的
處理器。原始的 sandbox:// URI 永遠不會被丟進 window.open()。
sandbox:// 交接卡
當附件卡片的 defaultAction 指向 sandbox:// 這個自訂 scheme 時,SDK 會在走到「開新分頁」的預設行為之前先攔截它,解析成一個具型別的 intent,再交給對應的處理器。一般的 https:// 連結不受影響,照常開連結。
<Chatbot
config={{ botProviderEndpoint: '...' }}
customChannelId="my-channel"
// 攔截 sandbox://<name>/open-browser
onSandboxOpenBrowser={(sandboxName) => { /* … */ }}
// 攔截 sandbox://<name>/open-file?absolute_path=…
onSandboxOpenFile={(sandboxName, absolutePath) => { /* … */ }}
// 攔截 sandbox://<name>/open-folder?absolute_path=…
onSandboxOpenFolder={(sandboxName, absolutePath) => { /* … */ }}
/>已派送的 intent
尚未點擊任何 sandbox:// 卡片
點左側前三張卡片 → 解析出的 intent 會記在下方;第四張是一般連結,不會被攔截。
URI 格式
sandbox://<sandboxName>/<action>?<query>
目前支援三個 action:
| URI | 解析結果 |
|---|---|
sandbox://sbx-a/open-browser | { kind: 'open-browser', sandboxName: 'sbx-a' } |
sandbox://sbx-a/open-file?absolute_path=%2Fhome%2Fuser%2Freport.md | { kind: 'open-file', sandboxName: 'sbx-a', absolutePath: '/home/user/report.md' } |
sandbox://sbx-a/open-folder?absolute_path=%2Fwork%2Foutput | { kind: 'open-folder', sandboxName: 'sbx-a', absolutePath: '/work/output' } |
absolute_path 必須做 URL 編碼(絕對路徑裡有很多 /)。
為什麼資料夾要自己一個 action
檔案和資料夾的終點不一樣:檔案進檢視器(讀內容,再掛上變更追蹤),資料夾只把樹展開到 那一層、選取它,不讀內容也不追蹤變更。
那為什麼不共用 open-file、讓面板自己判斷?因為卡片抵達的當下,面板還沒列過那一層,它分不出
這個路徑是檔案還是目錄。「先當檔案試、失敗再退回目錄」也不是可行的替代方案——後端對目錄的
fs/file 會回 500,fs/watch 的連線則直接被切斷。所以終點由卡片自己說明;這也是
RequestedFile.kind 為必填的原因。
卡片到站時要不要順便把內建側欄拉出來,兩種卡看的都是 autoRevealOnOpenFileCard(預設
true)。名稱裡的 OpenFileCard 早於資料夾卡存在,為相容而保留——「卡片可不可以把面板拉
出來」是同一個問題,不需要兩個旗標。
接手處理
三個 action 都可以由整合方覆寫:
<Chatbot
config={{ botProviderEndpoint: "..." }}
customChannelId="my-channel"
// 有給就以你的為準;沒給則走 SDK 預設(取一次性 URL 後開新分頁)
onSandboxOpenBrowser={(sandboxName) => { /* … */ }}
// open-file 的目的地通常是 File Explorer 的檢視器
onSandboxOpenFile={(sandboxName, absolutePath) => {
controller.requestFile(sandboxName, absolutePath);
}}
// open-folder 的目的地是檔案樹,不是檢視器
onSandboxOpenFolder={(sandboxName, absolutePath) => {
controller.requestFolder(sandboxName, absolutePath);
}}
// 預設處理器開分頁的目標,預設 '_blank'
sandboxBrowserOpenTarget="_blank"
// 想真的接管 open-file 就一定要加這行,理由見下方
fileExplorer="off"
/>
onSandboxOpenBrowser 是真的二選一:有給就以你的為準,沒給才走 SDK 預設。
onSandboxOpenFile 和 onSandboxOpenFolder 都不是。 它們是「加上去」而不是「取代掉」——你的
handler 會被呼叫,但內建的 File Explorer 側欄同時也照樣會被拉開,因為
那段邏輯只看 fileExplorer prop,不看你有沒有給 callback。而 fileExplorer 預設就是 'builtin'。
結果是你自己的 controller 和 Chatbot 內部的 controller 會同時收到同一個 requestFile 或
requestFolder。要真的接管,必須另外把 fileExplorer 設成 "off"。
自己解析
resolveSandboxUri 也可以單獨使用——例如你自己渲染卡片的時候:
import { resolveSandboxUri } from "@asgard-js/core";
const intent = resolveSandboxUri(uri);
if (intent === null) {
// 不是 sandbox://、action 不認得、或缺 absolute_path
// → 當成一般卡片處理,絕對不要 window.open() 原始 URI
}
回傳型別:
type SandboxUriIntent =
| { kind: "open-browser"; sandboxName: string }
| { kind: "open-file"; sandboxName: string; absolutePath: string }
| { kind: "open-folder"; sandboxName: string; absolutePath: string };
解析用的是手寫 regex 而不是 new URL()。絕對路徑帶有大量 /,而 WHATWG URL parser 對
非特殊 scheme 的 host 大小寫處理並不可靠;query 的部分仍交給 URLSearchParams 解碼。
一般連結不受影響
https:// 之類的 defaultAction 完全照舊走正常開連結行為——攔截只發生在 sandbox://。
也看看
- File Explorer —
open-file與open-folder的落點 - 冷啟動 HUD — sandbox 啟動與
launchedSandboxes - 訊息模板 — 附件卡片本身的結構