/** * 崩溃/异常终止时通过 Gateway API 发送邮件通知用户。 * * 设计约束: * - 不依赖任何外部库(崩溃场景下库可能已损坏) * - 只用 Node.js 内置 http/https * - 所有错误静默——通知失败不能让进程挂得更久 * - 只在崩溃路径(uncaughtException/unhandledRejection/exit(1))调用 * * Gateway 环境变量(同插件主进程): * - AGENTMAIL_GATEWAY_URL(默认 http://127.0.0.1:8180) * - AGENTMAIL_AGENT_KEY(agent 密钥,用于 API 认证) * - AGENTMAIL_AGENT_NAME(发送者名称,如 pi/dsh/opencode) * * 注意:如果 Gateway 本身不可达(崩溃根因是 Gateway 挂了),通知发不出去—— * 这是可接受的:Gateway 挂了邮件也发不出去,用户应该监控 Gateway 的 systemd 状态。 */ import http from 'node:http'; const GATEWAY_URL = process.env.AGENTMAIL_GATEWAY_URL || 'http://127.0.0.1:8180'; const AGENT_NAME = process.env.AGENTMAIL_AGENT_NAME || 'unknown-bridge'; const AGENT_KEY = process.env.AGENTMAIL_AGENT_KEY || ''; /** * 向 Gateway 发送崩溃通知邮件。 * @param {string} reason 终止原因描述 * @param {Error} [err] 原始错误对象(可选) */ export function notifyCrash(reason, err) { if (!AGENT_KEY) return; // 没有密钥,没法发 const body = JSON.stringify({ to: 'jianf@', subject: `[${AGENT_NAME}] 桥进程异常终止`, body: [ `**${AGENT_NAME}** 桥进程遇到未处理异常,已终止。`, '', `**原因**:${reason}`, err ? `**错误信息**:${err.message || String(err)}` : '', err?.stack ? `\n**堆栈**(首 500 字):\n\`\`\`\n${err.stack.slice(0, 500)}\n\`\`\`` : '', '', `**自动重启**:systemd 将在 5~10 秒内自动拉起。`, `如果反复崩溃,请检查日志:`, `- \`journalctl -u ${AGENT_NAME}-bridge -n 50 --no-pager\``, `- \`journalctl -u ${AGENT_NAME} -n 50 --no-pager\``, ].filter(Boolean).join('\n'), }); const parsed = new URL(`${GATEWAY_URL}/api/v1/agent/send`); const req = http.request({ hostname: parsed.hostname, port: parsed.port || 80, path: parsed.pathname, method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': AGENT_KEY, 'Content-Length': Buffer.byteLength(body), }, timeout: 5000, // 5 秒超时 }, () => {}); req.on('error', () => {}); // 静默 req.on('timeout', () => { req.destroy(); }); req.write(body); req.end(); }