aaif-goose / aaif-goose/goose

ACP provider: cancelling a prompt doesn't interrupt the agent; UI stays locked until the turn finishes

オープン
#10,619 コメント 5 件 リアクション 0 件 担当者 1 名 @lifeizhou-ap が担当を希望しています GitHub で見る
主要言語
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

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。