Files
HomeAgent/cmd/gui
JianFeeeee 85d76ad495 fix(gui): 设备桥补心跳与断线重连,兑现 liveness 门禁
## 背景

`device-bridge-liveness.test.mjs`(判据)已在 main 上,但它要求的实现
一直缺席,跑起来是:

    Error: cannot find function sendDeviceControlFrame in main.js

判据注释描述了两个洞,合起来的效果是「一次失联,终身失联」:
  1. 不发 ping —— waiter 侧有 pingLoop,GUI 侧一次都没发过;
  2. 不重连 —— startDeviceBridge 失败就 console.error 结束。

我先前只补了 ping 且命名为 sendDevicePing,判据仍红;本次按判据契约补全。

## 本次实现

**通用控制帧** `sendDeviceControlFrame(sock, opcode, payload)`
控制帧有 ping(0x9)/pong(0xA)/close(0x8) 三种,签名写死一种就换不来另外两种。

**心跳** 每 30s 一个 0x9 帧(与 waiter 的 pingLoop 对齐)。
起在 doConnectDeviceBridge 而非 connectDeviceWS —— 跟着「一条实际连上的
桥」而不是「某次调用」,后者在重连路径上会漏。

**重连** `scheduleDeviceReconnect`:线性退避 base×n、封顶 60s,
成功即归零(否则「昨天断十次、今天断一次」要等 10×base 才有第一次重试)。
退避先递增后算:head 用 max(1,attempt) 会让 attempt 0 与 1 都算出 base,
出现「退避头两次一样」的假象。

三条排程路径,缺一条就终身失联:
  - socket close(已连上后被关)
  - connectDeviceWS reject(**没有** close 事件,最易漏)
  - deviceWanted闸:用户主动停用后不得再排程,否则把已关的桥偷偷拉起来

## 顺带修掉的隐性故障

`connectDeviceBridgeOnce` 里 host/cpus/process/app 等推导原先裸调。
它们在 ws.send 的实参里,抛错会被上层 catch 吞成「连接失败」—— 而实际
连接是好的,表现为「反复重连但永远连不上」。现已逐项 try 包裹。

## 判据分工(避免两个门禁互相打架)

删掉 device-gateway-discovery.test.mjs 里 6 条 keepalive 文本判据
(grep 0x89、找 setInterval 间隔…)。它们只能证明「代码里出现过这些
字符」,证明不了「心跳真发出去了」。liveness 那份用 node:vm 跑真实函数,
是强判据;弱的那份会在实现重构时先红、掩盖真信号。

## 验证

device-bridge-liveness 14 条全绿(改前是 0 条可跑)。
另外 7 个门禁全绿:device-gateway-discovery / device-align /
endpoint-align / connections-io / retry-guard / sse-backoff /
package-config。

对齐过程中判据自身报过 3 次假红(process 未注入、doConnectDeviceBridge
无 return、ws 为 undefined),根因都已写进代码注释。
2026-10-05 14:37:43 +08:00
..