chore: directory migration - gateway→server, web→client/electron
This commit is contained in:
@ -32,7 +32,7 @@
|
||||
*
|
||||
* # 为什么必须共用
|
||||
*
|
||||
* 标记格式是**服务端正则的镜像**(`gateway/internal/handler/rename_proposal.go`)。
|
||||
* 标记格式是**服务端正则的镜像**(`server/internal/handler/rename_proposal.go`)。
|
||||
* 各平台各写一遍拼接,某一处少个空格或把双引号写成单引号,服务端匹配不上 ——
|
||||
* 而失败是静默的:邮件照常发出,提议凭空消失,模型以为自己提过了。
|
||||
*/
|
||||
|
||||
@ -1789,6 +1789,23 @@ export function apply(ctx: any, config: PluginConfig): void {
|
||||
case 'permission_decision':
|
||||
handlePermissionDecision(data);
|
||||
break;
|
||||
case 'session_update': {
|
||||
// 人类在 WebUI 切换权限档位后,DSH 运行时必须立刻更新 sandbox/mode +
|
||||
// approval/policy,否则模型仍按旧档位执行。
|
||||
const sid = String(data?.session_id || '');
|
||||
const pm = String(data?.permission_mode || '');
|
||||
if (sid && pm) {
|
||||
const bound = sessionMap.peek(sid);
|
||||
if (bound) {
|
||||
const live = ctx.agents.get(bound.dshSessionId);
|
||||
if (live?.session) {
|
||||
applyPermissionMode(live.session, pm);
|
||||
console.error(`[dsh-mail-bridge] session_update ${sid} -> 权限档位 ${pm}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'session_archived':
|
||||
// 会话归档 = 那条会话再也不会收信,映射可以确定性地清掉(不必等上限淘汰)。
|
||||
forgetSession(String(data?.session_id || ''));
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
} from '../lib/rename-proposal.js';
|
||||
|
||||
// 这一组测试钉住的是「插件拼的标记与服务端正则逐字符对应」。
|
||||
// 服务端那条正则在 gateway/internal/handler/rename_proposal.go:
|
||||
// 服务端那条正则在 server/internal/handler/rename_proposal.go:
|
||||
// <!--\s*agentmail:rename-session\s+alias="([^"]*)"(?:\s+reason="([^"]*)")?\s*-->
|
||||
// 拼错不会报错 —— 邮件照常发出,提议凭空消失。
|
||||
|
||||
|
||||
55
plugins/opencode-mail-bridge/lib/crash-notify.js
Normal file
55
plugins/opencode-mail-bridge/lib/crash-notify.js
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 崩溃/异常终止时通过 Gateway API 发送邮件通知用户。
|
||||
*
|
||||
* 约束同 pi-mail-bridge/lib/crash-notify.mjs:
|
||||
* - 不依赖外部库,只用 Node 内置 http
|
||||
* - 所有错误静默
|
||||
* - 只在崩溃路径调用
|
||||
*/
|
||||
|
||||
const http = require('http');
|
||||
|
||||
const GATEWAY_URL = process.env.AGENTMAIL_GATEWAY_URL || 'http://127.0.0.1:8180';
|
||||
const AGENT_NAME = process.env.AGENTMAIL_AGENT_NAME || 'opencode';
|
||||
const AGENT_KEY = process.env.AGENTMAIL_AGENT_KEY || '';
|
||||
|
||||
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 将在 10 秒内自动拉起。`,
|
||||
`如果反复崩溃,请检查日志:`,
|
||||
`- \`journalctl -u ${AGENT_NAME}-serve -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,
|
||||
}, () => {});
|
||||
|
||||
req.on('error', () => {});
|
||||
req.on('timeout', () => { req.destroy(); });
|
||||
req.write(body);
|
||||
req.end();
|
||||
}
|
||||
|
||||
module.exports = { notifyCrash };
|
||||
@ -32,7 +32,7 @@
|
||||
*
|
||||
* # 为什么必须共用
|
||||
*
|
||||
* 标记格式是**服务端正则的镜像**(`gateway/internal/handler/rename_proposal.go`)。
|
||||
* 标记格式是**服务端正则的镜像**(`server/internal/handler/rename_proposal.go`)。
|
||||
* 各平台各写一遍拼接,某一处少个空格或把双引号写成单引号,服务端匹配不上 ——
|
||||
* 而失败是静默的:邮件照常发出,提议凭空消失,模型以为自己提过了。
|
||||
*/
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
} from '../lib/rename-proposal.js';
|
||||
|
||||
// 这一组测试钉住的是「插件拼的标记与服务端正则逐字符对应」。
|
||||
// 服务端那条正则在 gateway/internal/handler/rename_proposal.go:
|
||||
// 服务端那条正则在 server/internal/handler/rename_proposal.go:
|
||||
// <!--\s*agentmail:rename-session\s+alias="([^"]*)"(?:\s+reason="([^"]*)")?\s*-->
|
||||
// 拼错不会报错 —— 邮件照常发出,提议凭空消失。
|
||||
|
||||
|
||||
68
plugins/pi-mail-bridge/lib/crash-notify.mjs
Normal file
68
plugins/pi-mail-bridge/lib/crash-notify.mjs
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 崩溃/异常终止时通过 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();
|
||||
}
|
||||
@ -32,7 +32,7 @@
|
||||
*
|
||||
* # 为什么必须共用
|
||||
*
|
||||
* 标记格式是**服务端正则的镜像**(`gateway/internal/handler/rename_proposal.go`)。
|
||||
* 标记格式是**服务端正则的镜像**(`server/internal/handler/rename_proposal.go`)。
|
||||
* 各平台各写一遍拼接,某一处少个空格或把双引号写成单引号,服务端匹配不上 ——
|
||||
* 而失败是静默的:邮件照常发出,提议凭空消失,模型以为自己提过了。
|
||||
*/
|
||||
|
||||
@ -399,5 +399,7 @@ function shutdown(reason) {
|
||||
main().catch((e) => {
|
||||
log(`启动失败: ${describeError(e)}`);
|
||||
releaseLock();
|
||||
// 故障邮件由 systemd ExecStopPost 统一发送:它还能覆盖 SIGKILL/OOM,
|
||||
// 而进程内的 uncaughtException 钩子覆盖不了这些真正的意外终止。
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
} from '../lib/rename-proposal.js';
|
||||
|
||||
// 这一组测试钉住的是「插件拼的标记与服务端正则逐字符对应」。
|
||||
// 服务端那条正则在 gateway/internal/handler/rename_proposal.go:
|
||||
// 服务端那条正则在 server/internal/handler/rename_proposal.go:
|
||||
// <!--\s*agentmail:rename-session\s+alias="([^"]*)"(?:\s+reason="([^"]*)")?\s*-->
|
||||
// 拼错不会报错 —— 邮件照常发出,提议凭空消失。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user