# 待決任務與取消

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

## 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.3.0 起 TUI 執行也能把工作留在背景，v1.3.2 起經由 `background_command` 與 `background_subagents`；僅限來源為 `cli-` 且沒有 deliver 目標的請求（不再取決於是否設定 decision model）。`run_command_background` 更名為 `background_command`（移除於 v1.3.2）；`run_websocket` 與 `subagents` 的 `background` / `timeout_min` 參數已移除（移除於 v1.3.2），請改用 `background_command` 與 `background_subagents`。每次呼叫開一條 leg，id 為 `cmd_` 或 `sub_` 加 8 個十六進位字元。v1.3.8 起同一任務在同一回合開的 leg 組成一個 job，存為 `~/.config/agenvoy/sessions/<session>/background/job_<8 hex>.json`，不再記錄在任務的 pending 快照中（移除於 v1.3.8）；兩個工具都接受 `objective`（這組要達成什麼，需能獨立閱讀）與 `next`（拿到報告後要做什麼），整組取第一個非空值。同一 session 同時最多 `MAX_BACKGROUND_TASKS` 條 leg，由兩個工具共用（`DEFAULT_CONCURRENCY`：CPU 數的一半，至少 3）。leg 執行期間，輸出會附加到 `~/.config/agenvoy/sessions/<session>/background/<leg id>.log`，結束即刪除。完成的結果最多保留 32 KiB；執行中的 `background_command` 每次監看回報最多 8 KiB、合計 32 KiB，較舊的以 `...[N reports elided]...` 標記省略。v1.3.8 起發起的任務不再為了 leg 維持 pending：照常結束並寫入任務歷史，daemon 也不再於每份報告到達時以 `[Resumed Task — background task finished]` 訊息接回任務（移除於 v1.3.8）。報告的去向取決於發起的任務：

| 發起的任務 | 報告 |
| --- | --- |
| 執行中 | 在它下一輪開始時以 `[Background Report — background task finished]`（或 `... update]`）訊息加入；在它即將結束時才送到的報告會讓它多跑一輪 |
| 暫停或等待 `ask_user` | 保留在 job 中，任務恢復時併入恢復訊息 |
| 已結束 | 等該 job 的 leg 全部完成，才以新任務執行一次：隱藏的輸入以 `[Resumed Task — background group finished]` 開頭，帶 `objective`、`next`、所有報告與發起任務的 id（供 `chat_history(mode=read)` 讀取）。報告已全部被發起任務收走的 job 不會執行 |

daemon 啟動時會清除所有背景 job，連同 `background/` 目錄一併刪除，因此背景工作不會跨重啟延續。

`background_command` 一律監看輸出：新的一行含有錯誤關鍵字（`keywords`；預設 `error`、`err!`、`fail`、`fatal`、`panic`、`exception`、`traceback`、`segmentation fault`、`core dumped`、`out of memory`、`permission denied`、`command not found`、`no such file`）時，待輸出穩定（安靜 2 秒，最多等 10 秒）後，回報最新輸出（最多 8 KiB），指令則繼續執行；每 30 秒最多回報一次。設定 decision model 時，會先經 decision 檢查剔除無害的命中與跟上次報告重複的內容；檢查失敗則不經檢查直接回報，並註明檢查失敗；未設定 decision model 時每次命中都會回報。舊監看模式的 `interval` 與 `prompt` 參數已移除（移除於 v1.3.2）。`background_subagents` 的 leg 回報存檔報告的路徑；背景 leg 從不發起確認，而是回傳已蒐集的內容，並說明該呼叫仍需核准。

TUI 過去顯示 `Waiting for background tasks` 時，從該視窗送出的訊息會併入等待中的任務；此模式移除於 v1.3.8，因為發起的任務現在照常結束，新訊息就是一般請求。`POST /v1/send` 的 `pending_task` 指向仍 pending 且有背景 job 的任務時，仍視為該任務的後續輸入：接回該任務，任務執行中則改為 steer。恢復的任務若不是該 session 最新的任務、且沒有背景 job，會改用新的 task hash，避免回覆插進較舊的任務（v1.3.0）。

## 取消與暫停

自 v1.0.10 起取消帶有 cause（`context.WithCancelCause`）。只有標記 `runtime.ErrUserCanceled` 的取消 —— TUI 的 Ctrl+C、Esc popup 選 **Yes**，或確認框選 **Cancel task**（v1.3.3）—— 才會刪除 pending 任務；v1.3.9 起刪除前會先以 `[Canceled]` 回覆（任務原本失敗時附上最後的錯誤）寫入對話歷史，`chat_history` 仍查得到。其餘取消（含 popup 的 **Pause**，以及 v1.3.3 起確認框的 **Abort task**，即 `runtime.ErrUserAborted`）只結束該次執行，pending 紀錄保留，可由 `/pending` 或 `GET /v1/session/:id/task` 續接。v1.2.0 起 TUI 的 `/pending` 以 `pending` frame 經 daemon 續行；已存的問題透過 socket 以 inline 方式詢問，續行的執行串流回同一視窗。TUI 執行綁定其 socket 連線：關閉 TUI 會取消執行，但 cause 不是 `runtime.ErrUserCanceled`，因此 pending 任務保留。v1.3.0 起取消與暫停也會停止該任務的背景工作；取消接著刪除 pending 任務，暫停則保留。單一 leg 可從 TUI 背景任務選單取消（v1.3.8）。`/pending` 中讓背景工作仍在跑的任務無法選取的 `[Active]` 標記移除於 v1.3.8。恢復時若任務已被某個執行占用，回傳 `[Conflict] pending task: running`。

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）→ 清空輸入 → v1.3.8 起，session 有 leg 在跑時開啟背景任務選單：**No**、各 leg 與 **All**（兩條以上才有），選定後再確認一次；TUI 以 `background_cancel` frame 送出，daemon 取消該 leg、丟棄晚到的結果並結算其 job。`ask_user` 因非使用者取消的原因而失敗時，同樣不再丟棄 pending 任務。
