# 待決任務與取消

等待使用者的執行如何暫存，以及取消與暫停對執行中任務的影響。

## Pending registry

`internal/runtime/pending.go` 是由 prefix 路由的 confirm/ask registry，由主 agent 與任何 in-process subagent 共用。Producer（`toolCall` confirm、`ask_user` handler、`store_secret` handler）呼叫 `Ask(ctx, req)` 並阻塞於 per-entry buffered=1 的 reply channel。每個前端透過 `RegisterListener(prefix)` 註冊 listener —— web 儀表板用 `"chat-"`、Telegram / Discord listener 用 `"tg-"` / `"dc-"`（空 prefix 匹配全部）—— 並只透過 `PickNextMatch(prefix, accept)` 認領匹配的 entry。v1.2.0 起 TUI 不再自行註冊 listener：daemon 的每條 socket 連線以 `RegisterListenerMatch(prefix, match)` 註冊 `"cli-"`，只匹配 ctx 帶有該連線所發起視窗 window hash 的請求，並以 `ask` frame 轉送，因此 TUI 執行產生的確認或提問只會送到發起它的 TUI 視窗。受限路徑的確認會在 TUI 的密碼欄位要求系統密碼（sudo 憑證仍在快取期內則略過），由 daemon 以 `sudo -S -v` 驗證；有輸入密碼或驗證失敗時，以 `verify` frame 回報結果。連線關閉時，其尚未回答的問題一律以錯誤結束。ctx 取消會移除該 entry，使過期的 producer 絕不浪費一次人為互動。

Gate `HasListener(origin)` 檢查是否有 prefix 符合該 session 來源（`OriginOf` 辨識 `cli-`、`chat-`、`tg-`、`dc-`）的 listener 已註冊。帶 match 函式的 listener 只在請求的 ctx 通過時才算數，因此視窗已斷線的 TUI 執行會找不到 listener。這取代了舊的全域 `pending.Active atomic.Bool`，讓 TUI、web、Telegram、Discord 的 confirm 流程能並行運作而不互相阻塞。

自 v1.0.23 起 `ask_user` 依來源分流。來自 TUI 執行（來源 `cli-`）時，問題以 inline 請求經 `Ask` 送出，同一次執行拿到回答後繼續。來自其他來源時，先落地同一輪其他 tool 的結果，寫入 pending 快照（v1.1.2 起每筆 tool 結果截至 32 KiB、參數截至 4 KiB，先前為 4 KiB / 1 KiB；被截斷處留有 `...[N bytes elided]...` 標記，恢復時的 prompt 會要求模型在缺漏內容有影響時重跑同一呼叫），結束本次執行，待回答後以新 turn 恢復。

## 取消與暫停

自 v1.0.10 起取消帶有 cause（`context.WithCancelCause`）。只有標記 `runtime.ErrUserCanceled` 的取消 —— TUI 的 Ctrl+C，或 Esc popup 選 **Yes** —— 才會刪除 pending 任務。其餘取消（含 popup 的 **Pause**）只結束該次執行，pending 紀錄保留，可由 `/pending` 或 `GET /v1/session/:id/task` 續接。v1.2.0 起 TUI 的 `/pending` 以 `pending` frame 經 daemon 續行；已存的問題透過 socket 以 inline 方式詢問，續行的執行串流回同一視窗。TUI 執行綁定其 socket 連線：關閉 TUI 會取消執行，但 cause 不是 `runtime.ErrUserCanceled`，因此 pending 任務保留。

TUI 的 Ctrl+C 與 popup 選項以 `cancel` frame 送出（選 **Pause** 時帶 `pause`），daemon 只對該連線發起的任務以 `exec.Cancel(taskHash, cause)` 套用。v1.2.0 起 `exec.Run`（v1.2.1 前名為 `exec.Start`）在發出第一個事件前就產生 task hash 並登記取消函式，因此從第一個事件起即可取消；TUI 尚未收到任何 task hash 時要求的取消會先保留，待第一個 hash 到達再送出。不在 popup 中時，`Esc` 依序為：關閉開啟中的 `/` 指令面板 → 關閉提示框 → 取消執行（尚未顯示任何內容時直接取消，否則經由 popup）→ 清空輸入。`ask_user` 因非使用者取消的原因而失敗時，同樣不再丟棄 pending 任務。
