免費獲得的協定行為
以下這些全都是由投影推導出來的。它們沒有任何程式碼要你寫——而這正是它們容易被忘記的原因。
tools/list 裡究竟有什麼
不是所有已開啟的視窗——只有可操作的那些。這個清單是兩層篩選的乘積:
- 哪些視窗可操作 —— 模態會阻擋它底下的視窗(見下文)。
- 該視窗的哪些動作可用 —— 以
set_available(false)隱藏的動作不會出現。
換句話說,一個工具會出現的條件是:它所屬的視窗可操作且該動作本身可用。其餘一概不會出現;任何一層有變動都會重新計算清單並通知用戶端。
可操作性與模態鏈
開啟模態會改變 Agent 可以操作哪些視窗。Purview 會重新計算 operableWindowIds,並把被阻擋視窗的工具從 tools/list 中移除:這是表達「某個視窗目前不可用」最清楚的訊號。
let confirm = win.open_modal("Confirm"); // window-modal: blocks its owner
let alert = app.open_app_modal("Alert"); // app-modal: collapses to its own tree
confirm.close(); // operability is restored| 開啟方式 | 對可操作集合的影響 |
|---|---|
app.open(..) | 一個普通的頂層視窗 |
win.open_modal(..) | 模態開啟期間,其擁有者變為不可操作 |
app.open_app_modal(..) | 只有模態自身的樹狀結構仍可操作 |
這是交接,不是疊加
開啟模態並不會把它的工具加到父視窗的工具之上。父視窗的工具會消失,由模態的工具取而代之。一個正在父視窗上執行任務的 Agent 會發現那些工具就這樣不見了。
有三點值得記住:
只有鏈尾可操作。 對於 B → D1 → D2,B 和 D1 都被阻擋——D1 本身是模態,但它同時也是 D2 的擁有者。只有 D2 會提供工具。
無關的視窗不受影響。 模態阻擋的是它自己的擁有者,而不是整個應用程式。如果 A 開啟了模態,而 C 就在一旁,那麼 C 依然可操作、工具照舊。唯一的例外是應用程式層級的模態,它會把自身樹狀結構以外的一切都收縮掉。
伺服器端並沒有「凍結」任何東西。 可操作性決定的是「提供什麼」,它不是一把鎖。對一個剛被阻擋的工具發出的過期呼叫,仍會在執行期被驗證並以 blocked_by_modal 拒絕——參見錯誤與驗證。
設計準則
模態必須帶有能關閉自己所需的工具。如果父視窗的工具消失了,而模態又沒提供任何工具,Agent 就會徹底卡死——它連這個對話框都關不掉。
樂觀並行
每個視窗都帶有一個 version。工具呼叫可以附上 expectedVersion;一旦它已不再相符,Purview 就會在你的處理器執行之前以 stale_state(附帶當前版本)拒絕該次呼叫。兩個 Agent 不可能默默互相覆寫,而你什麼都不必寫就能得到這項保障。
其餘部分
openedWindowIds。 在處理器執行期間開啟的視窗會被追蹤,並在該次呼叫的結果中回報,讓 Agent 立刻得知它們的 id。
通知。 版本遞增會觸發資源更新通知,經合併後廣播給每一個 session。
遮蔽。 標記為敏感的欄位絕不會出現在 app://windows 中。
動態 tools/list。 只有當下可操作的視窗才會貢獻工具,並在每次變更時重新計算。