Commit 86f37a4be714c331d3c52e4514fbec023f6312bf

Authored by yibo
1 parent 4a688046

fix: Claude CLI 错误反馈到前端 + 致命错误自动中断

- ClaudeCliProtocol: stderr 检测 rate_limit/429/401 等致命错误时自动 reject sendPrompt Promise
- agent-chat.js: 监听 stderr/disconnected 事件并通过 SSE 推送到前端
- AgentChat.vue: error 事件停止 thinking 状态 + ACP 日志面板显示错误
data/goals/_index.json 0 → 100644
  1 +[]
0 2 \ No newline at end of file
... ...
data/system/agents/master/memory/short-term/session-1774547297290.md 0 → 100644
  1 +# Session 摘要
  2 +
  3 +- **时间**: 2026-03-26 17:48:17
  4 +- **对话ID**: conv-mn7rjae6
  5 +- **状态**: completed
  6 +- **工具调用**: 0 次
  7 +- **耗时**: 3分钟
  8 +
  9 +## 任务
  10 +我需要创建一个名为 ABC 的 AI 互动剧公司。这家公司的核心业务逻辑和组织架构诉求如下:
  11 +
  12 +一、
  13 +
  14 +## 结果
  15 +API Error: 429 {"type":"error","error":{"type":"rate_limit_error","message":"usage limit exceeded (2056)"},"request_id":"0614a461f8ade83e80e74a117efb3ac7"}
... ...
packages/acp-core/protocol/ClaudeCliProtocol.js
... ... @@ -96,12 +96,21 @@ class ClaudeCliProtocol extends EventEmitter {
96 96 }
97 97 });
98 98  
99   - // stderr 转发
  99 + // stderr 转发 + 致命错误检测
100 100 this._child.stderr.on('data', (data) => {
101 101 const msg = data.toString().trim();
102 102 if (msg) {
103 103 console.log('[ClaudeCliProtocol] stderr:', msg.substring(0, 300));
104 104 this.emit('stderr', msg);
  105 +
  106 + // 检测致命错误:如果 sendPrompt 正在等待,主动 reject 防止 Promise 永远挂起
  107 + if (this._promptReject && /rate.?limit|usage.?limit|429|401|403|authentication|api.?key|quota|billing/i.test(msg)) {
  108 + const err = new Error(`Claude CLI 致命错误: ${msg.substring(0, 500)}`);
  109 + this._promptReject(err);
  110 + this._promptResolve = null;
  111 + this._promptReject = null;
  112 + console.error('[ClaudeCliProtocol] 致命错误已触发 Promise reject');
  113 + }
105 114 }
106 115 });
107 116  
... ...
server/routes/agent-chat.js
... ... @@ -1734,12 +1734,31 @@ router.post('/:agentId/chat', async (req, res) => {
1734 1734 entry.protocol.on('session_update', onUpdate);
1735 1735 entry.protocol.on('acp_event', onAcpEvent);
1736 1736  
  1737 + // 监听 stderr / error / disconnect — 将 Claude CLI 错误实时推送到前端
  1738 + const onStderr = (msg) => {
  1739 + console.warn(`[AgentChat] stderr from ${agentId}:`, msg.substring(0, 300));
  1740 + sendSSE('acp_raw', { method: 'claude/stderr', params: { message: msg }, ts: Date.now() });
  1741 + // 如果包含严重错误关键词,额外发一条 error 事件
  1742 + if (/error|rate.?limit|429|403|timeout|ECONNREFUSED|failed/i.test(msg)) {
  1743 + sendSSE('error', { message: `⚠️ Claude CLI: ${msg.substring(0, 500)}` });
  1744 + }
  1745 + };
  1746 + const onDisconnected = ({ code, signal }) => {
  1747 + if (code !== 0) {
  1748 + sendSSE('error', { message: `Claude CLI 异常退出 (code=${code}, signal=${signal})` });
  1749 + }
  1750 + };
  1751 + entry.protocol.on('stderr', onStderr);
  1752 + entry.protocol.on('disconnected', onDisconnected);
  1753 +
1737 1754 // 发送 prompt 并等待完成
1738 1755 await entry.protocol.sendPrompt(prompt);
1739 1756  
1740 1757 // 清理监听
1741 1758 entry.protocol.off('session_update', onUpdate);
1742 1759 entry.protocol.off('acp_event', onAcpEvent);
  1760 + entry.protocol.off('stderr', onStderr);
  1761 + entry.protocol.off('disconnected', onDisconnected);
1743 1762  
1744 1763 // 持久化当前轮次的回复
1745 1764 if (fullOutput) {
... ...
web/src/views/AgentChat.vue
... ... @@ -564,7 +564,10 @@ function handleEvent(type, data) {
564 564 else if (data.event === 'start') subAgents.value[data.callId].status = 'running'
565 565 scrollToBottom(); break
566 566 case 'error':
567   - messages.value.push({ role: 'assistant', content: `❌ ${data.message}`, timestamp: new Date().toISOString() }); break
  567 + messages.value.push({ role: 'assistant', content: `❌ ${data.message}`, timestamp: new Date().toISOString() })
  568 + acpLog.value.push({ type: 'error', icon: '🚨', text: data.message, ts: Date.now() })
  569 + thinkingText.value = ''; thinkingContent.value = ''
  570 + scrollToBottom(); scrollAcpLog(); break
568 571 }
569 572 }
570 573  
... ...