diff --git a/deploy/service-failure-notify.mjs b/deploy/service-failure-notify.mjs index ea186aa..bded842 100644 --- a/deploy/service-failure-notify.mjs +++ b/deploy/service-failure-notify.mjs @@ -213,6 +213,26 @@ export async function flushSpool(env = process.env) { return { total: names.length, sent, failed }; } +/** + * 把发送失败翻译成一句能定位原因的话。 + * + * `postPayload` 对非 2xx 抛的是 `Gateway HTTP : ` —— 里面已经带着 + * 状态码与响应体,直接打出来就够定位了。只有 fetch 本身抛(DNS/连接/超时)才真的是 + * 「不可达」,此时按名字区分开。 + */ +function describeSendError(error) { + const message = String(error?.message || error || ''); + const httpMatch = message.match(/^Gateway HTTP (\d{3}): ([\s\S]*)$/); + if (httpMatch) { + return `网关可达,但返回 HTTP ${httpMatch[1]}:${httpMatch[2].slice(0, 300)}`; + } + if (error?.name === 'AbortError') return '请求超时(网关未在限定时间内响应)'; + if (/fetch failed|ECONNREFUSED|ENOTFOUND|EHOSTUNREACH/i.test(message)) { + return `网关不可达:${message}`; + } + return message || '未知错误'; +} + async function main(argv = process.argv.slice(2), env = process.env) { const serviceName = argValue(argv, '--service'); if (argv.includes('--flush')) { @@ -234,9 +254,25 @@ async function main(argv = process.argv.slice(2), env = process.env) { env, serviceName, }); - console.error(result.sent - ? `[agentmail-failure-notify] ${serviceName} 异常已上报` - : `[agentmail-failure-notify] Gateway 不可达,报告已暂存 ${result.path}`); + if (result.sent) { + console.error(`[agentmail-failure-notify] ${serviceName} 异常已上报`); + return; + } + /* + * 区分「连不上」与「连上了但被拒」。 + * + * 原先两种情况都打印「Gateway 不可达」。实测代价:切换插件时 dsh 进了崩溃循环, + * 通知脚本连打六条「Gateway 不可达」并写进 spool —— 而网关**一直在正常服务** + * (NRestarts=0),真实响应是 **HTTP 403**(同一会话连续中继邮件撞上防互相唤醒 + * 的跳数上限,`maxRelayHops=5`)。 + * + * 那句话会把人送去查网络,而问题在策略层。故障通知本身给出误导性诊断, + * 是「静默失败」的另一种形态。 + */ + console.error( + `[agentmail-failure-notify] ${serviceName} 异常上报失败,报告已暂存 ${result.path}` + ); + console.error(`[agentmail-failure-notify] 原因:${describeSendError(result.error)}`); } const isCLI = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);