agentscope-ai / agentscope-ai/QwenPaw
Bug: Cursor ACP Runner — 扩展方法处理违反 JSON-RPC 协议导致 WritableIterable is closed
- 主要言語
- Python
- スター
- 34.9k
- フォーク
- 3.1k
- 平均マージ
- 1日 15時間
- マージ済み PR(30日)
- 225
説明
# Bug Report: Cursor ACP Runner — 扩展方法处理违反 JSON-RPC 协议导致流崩溃
## 环境
- QwenPaw 版本:当前最新版(venv 安装)
- OS:macOS (Darwin 23.2.0, arm64)
- Cursor CLI:`/Users/hollay/.local/bin/agent`(`agent acp` 模式)
- ACP Runner 配置:`tool_parse_mode: call_title`, `trusted: true`
## 问题概述
使用 `delegate_external_agent` 工具通过 ACP 协议与 Cursor CLI 交互时,每次任务完成后 Cursor 端稳定复现 `Error: RetriableError: WritableIterable is closed` 错误。任务结果本身完整送达,但会话流在收尾阶段异常关闭。
**影响**:简单任务结果可正常接收;但长任务的流式进度上报可能受影响,且 cursor 的扩展方法(如 `cursor/ask_question`、`cursor/create_plan`)无法正常工作。
## 复现步骤
1. 配置 cursor ACP runner:
```json
{
"enabled": true,
"command": "/Users/hollay/.local/bin/agent",
"args": ["acp"],
"env": {},
"trusted": true,
"tool_parse_mode": "call_title",
"stdio_buffer_limit_bytes": 52428800
}
```
2. 调用 `delegate_external_agent(action="start", runner="cursor", message="执行 echo test")`
3. 任务正常完成并返回结果
4. **每次**任务完成后末尾出现:`Error: RetriableError: WritableIterable is closed`
5. 稳定复现(两次独立测试均出现)
## 根因分析
### 问题 1(P0):`client.py` 对 Cursor 扩展方法抛错误,违反 JSON-RPC 协议
**文件**:`qwenpaw/agents/acp/client.py`,`ACPHostedClient` 类
**当前代码**(约第 280-291 行):
```python
async def ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
_ = params
self._unsupported_method(method)
async def ext_notification(self, method: str, params: dict[str, Any]) -> None:
_ = params
self._unsupported_method(method)
def _unsupported_method(self, method: str) -> NoReturn:
raise RequestError(
code=-32601,
message=f"Unsupported ACP extension method: {method}",
)
```
**问题**:QwenPaw 对**所有** Cursor 扩展方法(包括通知类)抛 `-32601` 错误。
根据 [Cursor 官方 ACP 文档](https://cursor.com/cn/docs/cli/acp),Cursor 发送两类扩展方法:
| 方法 | 类型 | 文档要求 | QwenPaw 当前行为 | 后果 |
|---|---|---|---|---|
| `cursor/ask_question` | 阻塞 | 必须返回 JSON-RPC 响应(answered/skipped/cancelled) | 抛 `-32601` 错误 | cursor 收到 error 响应,行为未定义 |
| `cursor/create_plan` | 阻塞 | 必须返回响应(accepted/rejected/cancelled) | 抛 `-32601` 错误 | 同上 |
| `cursor/update_todos` | **通知** | 即发即弃,**无需响应** | 抛 `-32601` 错误 | ⚠️ **协议违规**:对无 id 的通知返回 error response |
| `cursor/task` | **通知** | 即发即弃 | 抛错误 | 同上 |
| `cursor/generate_image` | **通知** | 即发即弃 | 抛错误 | 同上 |
**关键**:JSON-RPC 2.0 规定**通知(无 id)不应收到响应**。QwenPaw 对通知也抛 error → Cursor 的 ACP server 收到意外响应 → 流状态错乱 → `WritableIterable is closed`。
### 问题 2(P1):`service.py` 缺少 authenticate 步骤
**文件**:`qwenpaw/agents/acp/service.py`,`_open_conversation` 方法
**当前流程**:`spawn_agent_process` → `conn.initialize()` → `conn.new_session()`
**文档要求流程**:`initialize` → `authenticate(methodId: "cursor_login")` → `session/new` → `session/prompt`
缺少 `authenticate` 步骤。目前靠 `agent login` 预认证绕过,但如果 Cursor token 过期或未登录,会话会静默失败。
## 修复建议
### P0:修复 ext_method / ext_notification
```python
# ext_notification: 静默忽略通知(通知不需要响应)
async def ext_notification(self, method: str, params: dict[str, Any]) -> None:
# 通知类方法 fire-and-forget,无需响应
# 可选:记录 debug 日志
logger.debug("Ignoring ACP extension notification: %s", method)
return None
# ext_method: 返回 cancelled outcome 而非抛错误
async def ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
logger.debug("Received ACP extension method: %s (not implemented)", method)
# 返回 cancelled outcome,让 cursor 能正常继续
return {"outcome": "cancelled"}
```
### P1:补充 authenticate 步骤
在 `_open_conversation` 的 `conn.initialize()` 之后、`conn.new_session()` 之前,增加:
```python
try:
await conn.authenticate(method_id="cursor_login")
except Exception:
# 若已预认证(agent login / CURSOR_API_KEY),authenticate 可能失败但不影响会话
logger.debug("authenticate step skipped or failed (may be pre-authenticated)")
```
## 验证
修复后预期:
- `WritableIterable is closed` 错误消失
- Cursor 的 `cursor/ask_question` / `cursor/create_plan` 阻塞方法能正常收到 cancelled 响应并继续
- 通知类方法不再产生协议违规错误
## 附注
此问题在 `qoder-cli` runner 上不复现(qoder-cli 不发送 Cursor 特有的扩展方法),仅影响 cursor runner。codex runner 同样正常。
コントリビューションガイド
評価
この issue はまだ評価されていません。