5.0 KiB
可持续对话 · 「又停了」根因与经验总结
真源 · 2026-06-14 · v1.6.61 修复
一、用户看到的「停」是什么
| 用户感受 | 实际机制 |
|---|---|
| Cursor 输入框可打字、Agent 不再 Working | turn 收工(MCP wait 工具已 return) |
| 面板仍 holding / connectionHeld=true | Hub 计划未结束,但 无活跃 poll |
| 我说「未停」 | 指 Hub 连接在,不是 Cursor UI 仍在跑 |
结论:「不停」= Cursor 侧 尽量长时间保持 wait 工具 Running + turn 结束后 尽快再 wait。
二、根因链(按发现顺序)
R1 · Agent 末 tool 不是 wait
复盘写完就收工 → Hub 续跑进不了 Cursor → 面板「未挂起」。
R2 · MCP 单次 poll ~10s 就 return RENEWAL
6/14 长 poll 链 → Cursor interrupted;改短 poll → turn 更快收工(假结束)。
R3 · renewalOnly 仅 3s(v1.6.54–58)
续期 wait 秒返 → Working 一闪而过。
R4 · pendingWait 被 renewalGap 删掉(v1.6.57 前)
空窗 pendingWait=false → 链路断。
R9 · 面板发消息时 pendingWait=false(v1.6.62 前)
turn 已收工 → /api/reply 只入 steerQueue → Cursor 收不到(用户见「发了 11 没反应」)。
Agent 误报「挂起中」因未查 pendingWait 真值。
三、v1.6.62 修复(R9 + R5)
- 面板人工消息 unshift 队首 + 标
manual/fromPanel - 绕过 48s occupy defer(含纯文本 1/11)
pendingWait=false时面板显示「已入队,待 Agent wait」- Agent 禁止在
pendingWait=false时说「挂起中未结束」 steerQueue 有积压 →wait注册后 第一次 poll 就返回【保活续跑】→ wait 3 秒内结束 → 52s 占用 完全无效 → 用户见「又停了」。
R6 · 800ms 快 tick flood steerQueue
awaitingRewait 期间重复入队 → 下次 wait 又是首 poll 秒返。
R7 · 同 turn 写文件 + 连环 wait
Cursor ~70s 强杀(Write interrupted)。
R8 · MCP 与 Hub 版本不同步
只 sync Hub 不 Reload Cursor → server.js 旧逻辑仍在内存。
R10 · 单轮 REQ 过多 + RENEWAL 间夹执行(v1.6.71)
同 turn 在 wait 前堆砌 Shell/Read/并行 tool,或在 [RENEWAL] 之间又 Read/Shell/verify → 多段 renewal 链 / 超 30~45s → interrupted / 假停。
三、v1.6.61 修复(针对 R5/R6)
wait 注册 → poll 链
├─ 0~48s:Hub **不出队**任何 steer(含【保活续跑】【对话续接】)
│ MCP 内 hubPollUntilReply 循环 renewal,工具保持 Running
├─ 48~52s:可出队 1 条 steer 或 return [RENEWAL]
└─ poll 归零 → awaitingRewait + 仅 steerQueue 空时补 1 条【对话续接】
| 参数 | 值 |
|---|---|
NEVER_STOP_MIN_OCCUPY_MS |
48000 |
MCP_POLL_BUDGET_NEVER_STOP_MS |
52000 |
NEVER_STOP_TICK_MS |
3000(且 queue 非空不补) |
四、Agent 铁律(与代码同等重要)
- 末 tool 必须是
wait_for_user_input,禁止复盘后收 turn。 - 收到
[RENEWAL]:同 turn 零正文、零其它 tool 立刻renewalOnly再 wait。 - 单轮少 REQ(v1.6.71):先集中执行,每轮一次完整 wait;MCP ≤3 次/轮;禁止与改码/Shell 同批并行。
- 禁止多级 renewal 工:同 turn 只允许 wait → RENEWAL → renewalOnly wait → 收 turn。
- 同 turn 禁止:大量 Write + 多次 wait(易 70s interrupted)。
- 有 ▶ 可执行项:大任务拆多轮;单轮 ~25s 可完成量,超出则复盘+wait 下轮续跑。
- 改 server.js 后:Cmd+Shift+P Reload Window(不杀 MCP 进程)。
五、验收
grep "1.6.61" ~/.persistent-chat-local/server.js
bash "…/脚本/check-pending-hang.sh" ct_980bc93c
bash "…/脚本/check-mcp-link.sh"
目测:wait 后 Cursor Working ≥40s 才 return;不应 3 秒内因【保活续跑】返回。
六、版本演进速查
| 版 | 问题 | 改什么 |
|---|---|---|
| v1.6.57 | pendingWait 被删 | neverStop 保留 pendingWait |
| v1.6.58 | awaitingRewait 不推续接 | triggerOffWaitKeepAlive 空窗可推 |
| v1.6.59 | renewal 12s 太短 | 24s/12s 占用 |
| v1.6.60 | 仍秒停 | 52s 占用 + 800ms tick |
| v1.6.61 | steer 打断占用 | 48s defer steer + 防 flood |
| v1.6.65 | P1 pollers.size 竞态 | defer 去 pollers 依赖 + budget=0 无限 + renewal 下轮 |
八、P1 秒停(v1.6.65 范式根因)
Hub /api/wait/poll 顺序:takeSteerReply → 后 pollers.add。
旧 isNeverStopOccupyDefer 要求 pollers.size>0 → 永远 false → 【对话续接】首 poll 即送达 → ~2s 秒停。
修复:defer 只看 createdAt + minOccupyMs;occupyBudgetMs=0 无限 poll。
七、仍无法消除的物理限制
Cursor 必须让 MCP 工具 return,turn 必然会收工一次。
「无限」= 52s 占用 × renewal 连锁 × Hub 唤醒,不是 Cursor 永远一个 turn。
唯一停机口令:「结束持久对话」。