ACP provider: cancelling a prompt doesn't interrupt the agent; UI stays locked until the turn finishes
- 主要言語
- Rust
- スター
- 54.2k
- フォーク
- 6.2k
- 平均マージ
- 3日 4時間
- マージ済み PR(30日)
- 240
説明
## Summary
When goose uses an ACP agent as its provider (e.g. Claude Code via the `claude_acp` provider), cancelling a prompt does not interrupt the agent. The session stays in the "running" state — the desktop **send button stays disabled** and you cannot even queue a message — until the agent finishes the entire turn on its own, which can take many minutes.
## Root cause
Two independent gaps in the ACP client provider (`crates/goose/src/acp/provider.rs`) and the agent reply loop (`crates/goose/src/agents/agent.rs`) combine:
**1. goose never sends `session/cancel` to the ACP agent.** The client→agent request channel has no cancel path:
```rust
enum ClientRequest { // provider.rs:65
NewSession { … }
SetMode { … }
SetConfigOption { … }
Prompt { … }
}
```
There is no `Cancel` variant and nowhere does the client send a `session/cancel` notification. (goose only *handles* incoming cancels when it is itself the ACP server, in `server.rs`.) `Provider::stream()` (provider.rs:472) does not even take a cancellation token. So when the user cancels, goose sets its own cancel token but the agent is never told to stop and keeps running the whole turn.
**2. goose's cancellation is cooperative and the check can't fire while the stream is blocked.** The reply loop only checks the cancel token *after* a stream item arrives:
```rust
while let Some(next) = stream.next().await { // agent.rs:2047
if is_token_cancelled(&cancel_token) || exit_chat { // agent.rs:2048
break;
}
```
There is no `tokio::select!` racing the token against the stream. The ACP provider stream yields only when the agent emits an update:
```rust
while let Some(update) = rx.recv().await { … } // provider.rs:522
```
and it only ends when `response_tx` is dropped — which happens after `block_task().await` on the outstanding `PromptRequest` completes (provider.rs:1168), i.e. only when the agent fully finishes the turn.
### Resulting chain
1. User cancels → cancel token set, but no `session/cancel` sent → agent keeps running.
2. While the agent is mid-turn with no output (slow tool call, thinking), `rx.recv().await` pends → `stream.next().await` pends.
3. The loop never reaches the `is_token_cancelled` check at agent.rs:2048, so the set token is never observed.
4. The `reply` future stays alive → session stays "running" → desktop keeps **send disabled**.
5. Only when the agent finishes on its own does the stream end and the UI free up.
## Repro
Use Claude Code as an ACP provider, send a prompt that triggers a long agentic turn, then cancel. The send button remains disabled for the remainder of the agent's turn.
コントリビューションガイド
調査の方向性
この issue は `crates/goose/src/acp/provider.rs` と `crates/goose/src/agents/agent.rs` を指しています。まず `ClientRequest` enum と `Provider::stream()` を調べ、キャンセルパスを追加します。次に、返信ループを変更して `tokio::select!` を使い、キャンセルトークンとストリームを競合させます。テストでは Claude Code のような ACP provider を使用し、プロンプトをキャンセルすると UI が直ちに解放されることを確認します。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- rust
- 領域
- ai-infra-agents, backend-api-design
- issue の種類
- バグ
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 活発さ
- 静か
- 明瞭さ
- 明確に書かれている
- 初心者へのやさしさ
- 65/100