Add heartbeat event and daemon browser-liveness reaper

Introduce a `system.heartbeat` protocol event and use it as a
liveness signal on the daemon side.

- Track `last_seen` per connected browser, bumped on every inbound
  WS frame (tool response, event, ping/pong, or heartbeat).
- Add a reaper task that drops any browser silent past a 60s window
  and purges its sessions, mirroring the WS disconnect cleanup. This
  releases half-open connections left behind when the OS resumes from
  sleep and kills the service worker without delivering a close frame,
  so the daemon no longer pins a phantom "connected" browser and can
  idle-exit normally.
- Wire the reaper into both the foreground daemon and the test/embed
  entry point, aborting it on shutdown.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
drakezhang
2026-07-22 12:16:56 +08:00
co-authored by Cursor
parent ccf3c32140
commit 5e91178e03
8 changed files with 161 additions and 2 deletions
+8
View File
@@ -40,6 +40,14 @@ pub struct EventFrame {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum EventKind {
/// Application-level keepalive emitted by the extension roughly
/// every 20s while the WS link is up. Two purposes: (1) the
/// send/receive activity resets the MV3 service-worker idle timer
/// (Chrome 116+), keeping the worker — and therefore the socket —
/// alive during use; (2) the daemon treats it as a liveness signal
/// so a silently-dead browser can be reaped. Carries no payload.
#[serde(rename = "system.heartbeat")]
SystemHeartbeat,
#[serde(rename = "session.activity")]
SessionActivity,
#[serde(rename = "session.window_closed")]