Commit bb089e3f0e690702449a51587c02bfad1c44e6f2

Authored by yibo
1 parent 86f37a4b

fix: 添加 sendPrompt 120s 空闲超时保护

Claude CLI 遇到 API rate limit/hang 时可能既不输出 stdout 也不输出 stderr,
导致 sendPrompt Promise 永远挂起,前端卡在思考中状态。

- 新增 120s 空闲超时:无 stdout 活动自动 reject
- 有 stdout 活动时重置计时器(避免长任务误判)
- result 到达时清理计时器
packages/acp-core/protocol/ClaudeCliProtocol.js
... ... @@ -85,6 +85,9 @@ class ClaudeCliProtocol extends EventEmitter {
85 85 const lines = this._buffer.split('\n');
86 86 this._buffer = lines.pop() || '';
87 87  
  88 + // 有 stdout 活动 → 重置超时计时器
  89 + if (this._resetIdleTimer) this._resetIdleTimer();
  90 +
88 91 for (const line of lines) {
89 92 if (!line.trim()) continue;
90 93 try {
... ... @@ -182,6 +185,37 @@ class ClaudeCliProtocol extends EventEmitter {
182 185 this._promptResolve = resolve;
183 186 this._promptReject = reject;
184 187  
  188 + // 超时保护:如果 120 秒内没有任何 stdout 活动(API hang/rate limit 静默),自动 reject
  189 + const IDLE_TIMEOUT_MS = 120_000;
  190 + let idleTimer = setTimeout(() => {
  191 + if (this._promptReject) {
  192 + const err = new Error(`Claude CLI 响应超时 (${IDLE_TIMEOUT_MS / 1000}s 无活动)`);
  193 + console.error('[ClaudeCliProtocol]', err.message);
  194 + this._promptReject(err);
  195 + this._promptResolve = null;
  196 + this._promptReject = null;
  197 + this._idleTimer = null;
  198 + }
  199 + }, IDLE_TIMEOUT_MS);
  200 +
  201 + // 每次收到 stdout 数据时重置超时(说明 CLI 还活着)
  202 + this._idleTimer = idleTimer;
  203 + this._resetIdleTimer = () => {
  204 + if (this._idleTimer) {
  205 + clearTimeout(this._idleTimer);
  206 + this._idleTimer = setTimeout(() => {
  207 + if (this._promptReject) {
  208 + const err = new Error(`Claude CLI 响应超时 (${IDLE_TIMEOUT_MS / 1000}s 无活动)`);
  209 + console.error('[ClaudeCliProtocol]', err.message);
  210 + this._promptReject(err);
  211 + this._promptResolve = null;
  212 + this._promptReject = null;
  213 + this._idleTimer = null;
  214 + }
  215 + }, IDLE_TIMEOUT_MS);
  216 + }
  217 + };
  218 +
185 219 // Claude CLI stream-json 输入格式
186 220 const message = {
187 221 type: 'user',
... ... @@ -197,8 +231,10 @@ class ClaudeCliProtocol extends EventEmitter {
197 231 try {
198 232 this._child.stdin.write(jsonLine);
199 233 } catch (e) {
  234 + clearTimeout(idleTimer);
200 235 this._promptResolve = null;
201 236 this._promptReject = null;
  237 + this._idleTimer = null;
202 238 reject(new Error(`[ClaudeCliProtocol] stdin 写入失败: ${e.message}`));
203 239 }
204 240 });
... ... @@ -260,6 +296,7 @@ class ClaudeCliProtocol extends EventEmitter {
260 296 });
261 297  
262 298 if (this._promptResolve) {
  299 + if (this._idleTimer) { clearTimeout(this._idleTimer); this._idleTimer = null; }
263 300 this._promptResolve();
264 301 this._promptResolve = null;
265 302 this._promptReject = null;
... ...