狀態與任務:互動與背景工作並行

這一頁用 PromptSuggestion 做真實案例,從狀態欄位到互動行為,完整拆解一次「可見功能背後的執行路徑」。

90 秒總覽

promptSuggestion 不是單純 UI 提示,而是跨越「回合尾端 hook、AppState、鍵盤互動、telemetry、speculation」的小型子系統。它適合拿來理解:狀態欄位如何驅動互動行為。

執行路徑(PromptSuggestion 案例)

回合結束觸發 stop hook
executePromptSuggestion 啟動
檢查 suppress 條件(plan mode / pending permission / rate limit)
generateSuggestion 產生候選文字
filter 規則淘汰不合格內容
寫入 appState.promptSuggestion
UI 顯示 ghost text
使用者 Tab 或 Right 接受,或直接輸入忽略
flowchart TD
  A[stopHooks turn end] --> B[executePromptSuggestion]
  B --> C{suppress?}
  C -->|yes| D[記錄 suppressed]
  C -->|no| E[generateSuggestion]
  E --> F[shouldFilterSuggestion]
  F -->|drop| D
  F -->|keep| G[set appState.promptSuggestion]
  G --> H[Typeahead 顯示 ghost text]
  H --> I{使用者互動}
  I -->|Tab/Right| J[acceptedAt]
  I -->|直接送出| K[ignored]

路徑判讀重點

  • 建議是「寫入狀態」而非「直接送出命令」。
  • 可見 UI 行為依賴 shownAt/acceptedAt 兩個時間戳來判斷接受或忽略。
  • 同一路徑同時服務 CLI 與 SDK telemetry,因此 suppression reason 必須可追蹤。

關鍵決策(為什麼這樣設計)

決策 1:先寫 AppState,再由 UI 消費

原因:把建議當成狀態,才能讓 REPL、Typeahead、紀錄事件共享同一來源。

代價:狀態欄位變多,需維護 reset 與生命週期一致性。

決策 2:明確 suppression gate

原因:在權限提示、plan mode、限流時強推建議會干擾主流程。

代價:邏輯分支增加,需維護 suppressed reason 的語義穩定。

決策 3:用 forked agent 產生建議並守住 cache 約束

原因:降低主回合負擔,同時利用既有上下文。

代價:參數不能亂改,否則 cache hit rate 會顯著下降。

替代方案與取捨

方案 優點 缺點 為何未採用/何時可採用
直接在 UI 端規則推導建議 不需額外模型請求 上下文理解弱,容易給錯建議 僅適合 very simple command hints
每次都自動接受建議 操作步驟最少 高誤觸風險,破壞使用者控制權 與目前互動哲學衝突,不採用
只記錄 accepted,不記錄 ignored 資料模型較簡單 無法評估建議品質與干擾程度 不利迭代,不採用

失敗路徑與防護

Failure 1:建議內容是 meta 文字或錯誤訊息

症狀:出現「nothing to suggest」等非使用者語句。

防護:shouldFilterSuggestion 做多重過濾規則。

Failure 2:使用者已在輸入,但 ghost text 仍覆蓋體驗

症狀:輸入中看到不相關建議。

防護:只有在 input 為空時顯示建議;提交後 reset 狀態。

Failure 3:speculation 與新輸入衝突

症狀:背景推演結果與使用者新方向不一致。

防護:接受/重置建議時中止 speculation,避免污染後續狀態。

實作驗證(你改完要怎麼確認)

  • 連續完成兩輪對話:確認第二輪後才會產生建議(early conversation gate)。
  • 打開 plan mode 或觸發 permission prompt:確認建議被抑制且不干擾主互動。
  • 空輸入按 Tab/Right:確認建議被接受並記錄接受路徑。
  • 直接輸入不同內容送出:確認被記為 ignored 並清空舊建議。

如果你要調整建議規則,至少重新驗證「抑制、接受、忽略」三條路徑。

← 上一頁 下一頁:模式與權限 →