69 lines
2.5 KiB
JavaScript
69 lines
2.5 KiB
JavaScript
/**
|
||
* 崩溃/异常终止时通过 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();
|
||
}
|