From 18618ad8ea4c55352c3516caeca2f856f66e0fc3 Mon Sep 17 00:00:00 2001 From: ekko <152005280+EKKOLearnAI@users.noreply.github.com> Date: Thu, 2 Jul 2026 09:54:04 +0800 Subject: [PATCH] fix bridge terminal error detection (#1890) --- ...07-02-issue-1881-bridge-terminal-errors.md | 11 ++++++ .../hermes/run-chat/handle-bridge-run.ts | 24 +++++++++---- .../run-chat-bridge-terminal-error.test.ts | 34 +++++++++++++++++++ 3 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 docs/chat-chain-changes/2026-07-02-issue-1881-bridge-terminal-errors.md diff --git a/docs/chat-chain-changes/2026-07-02-issue-1881-bridge-terminal-errors.md b/docs/chat-chain-changes/2026-07-02-issue-1881-bridge-terminal-errors.md new file mode 100644 index 00000000..030ac53e --- /dev/null +++ b/docs/chat-chain-changes/2026-07-02-issue-1881-bridge-terminal-errors.md @@ -0,0 +1,11 @@ +--- +date: 2026-07-02 +pr: 1890 +feature: Bridge terminal error detection +impact: Completed bridge runs no longer fail just because successful assistant text mentions HTTP status handling or a generic result message. +--- + +Issue #1881 showed successful assistant output being rendered as a failed run +when the response text mentioned HTTP status handling such as 401 redirects. +Terminal error detection now keeps explicit bridge or agent failure flags as +authoritative and only uses text matching for compact provider/API error shapes. diff --git a/packages/server/src/services/hermes/run-chat/handle-bridge-run.ts b/packages/server/src/services/hermes/run-chat/handle-bridge-run.ts index 8c0585e2..db9914a3 100644 --- a/packages/server/src/services/hermes/run-chat/handle-bridge-run.ts +++ b/packages/server/src/services/hermes/run-chat/handle-bridge-run.ts @@ -117,9 +117,18 @@ function shouldPollBridgeGeneratedTitle(sessionId: string): boolean { } function looksLikeAgentFailure(value: string): boolean { - return /\bAPI call failed after\b/i.test(value) - || /\bHTTP\s+(?:4\d\d|5\d\d)\b/i.test(value) - || /\b(?:401|403|429|500|502|503|504)\b/.test(value) && /\b(?:unauthorized|forbidden|rate limit|unavailable|failed|error)\b/i.test(value) + const text = value.replace(/\s+/g, ' ').trim() + if (!text) return false + + return /\bAPI call failed after\b/i.test(text) + || /\bHTTP\s+(?:4\d\d|5\d\d)\b/i.test(text) + || /\b(?:401|403)\b.{0,100}\b(?:unauthorized|forbidden|authentication|auth|invalid api key|permission denied)\b/i.test(text) + || /\b(?:unauthorized|forbidden|authentication|auth|invalid api key|permission denied)\b.{0,100}\b(?:401|403)\b/i.test(text) + || /\b429\b.{0,100}\b(?:rate limit|too many requests|quota)\b/i.test(text) + || /\b(?:rate limit|too many requests|quota)\b.{0,100}\b429\b/i.test(text) + || /\b(?:500|502|503|504)\b.{0,100}\b(?:server error|bad gateway|service unavailable|gateway timeout|upstream|provider|request failed|api)\b/i.test(text) + || /\b(?:server error|bad gateway|service unavailable|gateway timeout|upstream|provider|request failed|api)\b.{0,100}\b(?:500|502|503|504)\b/i.test(text) + || /(?:无可用渠道|渠道不可用|认证失败|鉴权失败|额度不足|余额不足|请求失败|接口调用失败|限流)/i.test(text) } export function bridgeTerminalError(chunk: Pick): string | null { @@ -129,19 +138,20 @@ export function bridgeTerminalError(chunk: Pick { }, } as any)).toBeNull() }) + + it('does not flag implementation notes that mention HTTP status handling', () => { + expect(bridgeTerminalError({ + status: 'complete', + result: { + completed: true, + final_response: [ + '全部完成,构建通过。', + 'src/api/request.js: Axios 实例 + 请求拦截器 + 响应拦截器(401 互斥跳转)。', + '登录按钮状态: normal / hover / active / loading / success / error / locked / disabled。', + ].join('\n\n'), + }, + } as any)).toBeNull() + }) + + it('does not treat a successful result message as an error', () => { + expect(bridgeTerminalError({ + status: 'complete', + result: { + completed: true, + message: '全部完成,构建通过。', + final_response: '全部完成,构建通过。', + }, + } as any)).toBeNull() + }) + + it('still surfaces compact auth errors in result messages when no final response exists', () => { + expect(bridgeTerminalError({ + status: 'complete', + result: { + message: 'HTTP 401: unauthorized', + }, + } as any)).toBe('HTTP 401: unauthorized') + }) })