三个平台桥原本各自手写 SSE 解析,有两个共同的静默丢事件缺陷:
1. evt/data 是每次 read() 的局部变量 —— TCP 把一帧
'event: x\ndata: {...}\n\n' 切在换行处时,前半段的 event 名被丢掉、
后半段只剩 data,整帧静默丢弃。表现为「新邮件偶尔收不到」
「权限决策点了没反应」,日志里一个字都没有。
2. 重连不带 Last-Event-ID —— 断线期间的事件留在服务端 per-agent 环形
缓冲里永远回放不出来(pi 与 homeagent 已正确使用,DSH/opencode 没有)。
修法:抽出共用 lib/sse-client.js(三桥逐字节同源,check-shared-libs 校验),
把「跨 chunk 保帧状态」与「Last-Event-ID 断点续传」写对一次。pi 桥的
gateway.mjs 也改为复用同一实现(保留 reconfigure 时清断点的语义)。
pi worker 丢任务:worker 未回报 done 就退出(SIGKILL/OOM/崩溃)时,
主进程原来只记一行日志就 pump() —— 那封邮件永远没有回音。改为按
1s/2s 退避有界重投(默认 3 次),到上限记「放弃」并可观测。
systemd 故障上报:四个宿主服务接入 service-failure-notify.mjs 的
ExecStopPost/--report 与 ExecStartPost/--flush。进程内 uncaughtException
捕获不了 SIGKILL/OOM,只能由 systemd 统一覆盖。正常 stop/restart 不发信。
仓库卫生:server/server(24MB 构建产物,f9d757b 误提交)移出版本库。
测试:opencode 302 / dsh 335 / pi 391 全绿(新增 12 例 SSE 帧解析 +
2 例 worker 重投);Go 全量通过;四平台重启后在线且无错误。
63 lines
3.0 KiB
Desktop File
63 lines
3.0 KiB
Desktop File
[Unit]
|
||
Description=pi mail-bridge (AgentMail ↔ @earendil-works/pi-coding-agent)
|
||
After=network-online.target agentmail-gateway.service
|
||
Wants=network-online.target
|
||
|
||
[Service]
|
||
Type=simple
|
||
|
||
# 桥自己不需要工作目录 —— 每条会话的 cwd 来自邮件寻址的 path 位。
|
||
# 但 systemd 要求一个存在的目录,且 pi 的 SettingsManager 会在这里找
|
||
# 项目级配置,因此指向仓库而不是 /。
|
||
WorkingDirectory=/home/program/agentmail/plugins/pi-mail-bridge
|
||
ExecStart=/usr/bin/node /home/program/agentmail/plugins/pi-mail-bridge/src/index.mjs
|
||
|
||
# pi 靠 HOME 定位 ~/.pi/agent(settings.json、auth.json、models.json、sessions/)。
|
||
# systemd 不会自动注入 HOME,不显式给就:
|
||
# - 读不到 provider 凭证 → 每封邮件都"没有可用模型"
|
||
# - 会话落到 /.pi 或直接失败 → C-8 的会话快照永远是空的
|
||
Environment=HOME=/root
|
||
|
||
# pi 的全局扩展 pi-a2a / pi-acp 绑死 127.0.0.1:12010 / 12011。
|
||
# 桥用 noExtensions:true 起会话,本进程不会去 bind 那两个端口;
|
||
# 这两个变量是给「同机还跑着 pi CLI」的情况留的隔离位,
|
||
# 万一将来放开扩展加载,端口也不会和交互式 pi 撞。
|
||
Environment=PI_A2A_PORT=13010
|
||
Environment=PI_ACP_PORT=13011
|
||
|
||
EnvironmentFile=/etc/agentmail/pi.env
|
||
|
||
Restart=always
|
||
RestartSec=10
|
||
|
||
# 桥用 O_EXCL 建 <AGENTMAIL_CONFIG_DIR>/pi-bridge.lock 保证单实例
|
||
# (两个实例各自订阅 SSE 会让同一封邮件各起一条会话,发件人收到两封回信,
|
||
# 而去重集合在各自内存里、彼此看不到)。
|
||
#
|
||
# 被 SIGKILL 或主机掉电时锁文件留在盘上 —— 桥自己会检查锁里的 pid 是否还活着,
|
||
# 但那要求 /proc 可读且 pid 未被复用。systemd 重启前清一次更直接:
|
||
# ExecStartPre 里的 pid 必然已经死了(systemd 确认进程退出后才重启)。
|
||
#
|
||
# 用 `-` 前缀:文件不存在时 rm 失败不该拦住启动。
|
||
ExecStartPre=-/bin/rm -f /root/.agentmail-pi/pi-bridge.lock
|
||
|
||
# 桥是长驻守护进程,启动即注册 + 立刻打一次心跳 + 订阅 SSE(B-1),
|
||
# 不像 opencode 那样惰加载,因此不需要预热。
|
||
#
|
||
# 异常退出邮件上报:进程内 uncaughtException 捕获不了 SIGKILL/OOM,只能由 systemd 覆盖。
|
||
# 正常 stop/restart 不上报(脚本内 isAbnormalExit 提前返回)。
|
||
ExecStopPost=-/usr/bin/node /home/program/agentmail/deploy/service-failure-notify.mjs --report --service pi-mail-bridge.service
|
||
# 补发上次 Gateway 不可达时暂存的报告(per-agent spool,不跨 Agent 混发)
|
||
ExecStartPost=-/usr/bin/node /home/program/agentmail/deploy/service-failure-notify.mjs --flush --service pi-mail-bridge.service
|
||
|
||
# pi 会话在内存里持有整条对话,长跑之后常驻几百 MB。给一个上限让它被
|
||
# OOM killer 挑中而不是拖垮整机;Restart=always 会把它拉回来。
|
||
MemoryMax=4G
|
||
|
||
# 优雅停机:桥收到 SIGTERM 后要注销 SSE、清锁文件。
|
||
# 默认 90 秒太长(停机时人在等),10 秒足够那两件事。
|
||
TimeoutStopSec=10
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|