← Commit history

Fix sleep/wake window loss: the 8s reaper was racing sleep-recovery and killing recovered windows. On wake, rescan reattaches a page then prunes it as the dead pre-sleep tab, leaving the session at 0 tabs with _lostBrowser already cleared, so the reaper deleted it before the healer relaunched. Now: re-mark any post-prune empty session as lost + refresh its heal window; the reaper never reaps a session the healer is still trying to recover (until it explicitly gives up)

John Lauer ·674307aefe ·3mo ago ·parent d8f96da
4 files changed +24−7
adom-bridge-puppeteer-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "1.9.304",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.304/adom-bridge-puppeteer-v1.9.304.zip",-  "sha256": "209ab37f2842054ad177fed445e2981ba31add5dc8a004a980f5ac844d706a94",-  "size": 727271,+  "version": "1.9.305",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.305/adom-bridge-puppeteer-v1.9.305.zip",+  "sha256": "a0cc84141ab8976b581a079b05bb6dc3cda151ffdf21b590ef489c2b1bb03a37",+  "size": 727817,   "verbPrefixes": [     "browser_",     "desktop_recorder_",
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.304+1.9.305
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "1.9.304",+  "version": "1.9.305",   "description": "Drives a browser ALREADY on the machine \u2014 installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) \u2014 launched with a FRESH isolated profile, so the common case has NO ~150 MB Chrome-for-Testing download and it never touches the user's real tabs/logins. Binds its local server to LOOPBACK (127.0.0.1) only, so no Windows Firewall 'allow Node through the firewall' prompt ever pops. Chrome for Testing is fetched only as a LAST RESORT if the machine has no Chromium browser at all. SPAWN-VERIFIED fallback: pup tries each detected browser (Chrome \u2192 Edge \u2192 CfT) and actually launches it, falling through to the next if one won't start (and handling browsers like Edge that relaunch onto another PID), so a corrupt/partial install never hard-fails an open. The browser that launches is cached as the default (persisted across restarts) so it isn't re-probed each time. On spawn the bridge SELF-WARMS: detects browsers + disk, and only if the box has no Chrome/Edge and no cached CfT does it background-fetch CfT so the first open is instant. browser_use pins a specific browser (chrome | edge | cft | auto); {browser:'chrome',install:true} downloads & installs REAL Google Chrome (user-scoped, no admin) on an Edge-only box. browser_readiness (READ-ONLY) reports which browser will be used + the full candidate list + the default + free disk. Every failure returns a specific errorCode + _hint (chrome_install_no_disk, chrome_download_network, chrome_install_permission, chrome_stable_not_detected, \u2026). Launches with FULL browser permissions by default for full debug (strictPermissions:true opts out). Headful automation: open/close windows+tabs, navigate, screenshot, eval JS, record. pup is a GENERIC, non-profile browser only \u2014 every session is a fresh isolated Chrome/Edge process, never the user's real signed-in browser; profile defaults to sessionId. To drive the user's REAL Chrome/Edge (their profile, cookies, SSO), use the Adom browser EXTENSION's nbrowser_* verbs \u2014 a separate bridge, not pup's job. macOS (v1.9.74+): full parity with mac-native mechanics \u2014 background-by-default is the app-level hide (windows stay drivable/screenshottable while hidden), raise/lower map to unhide+activate/hide, browser_alert_window posts a native toast whose click focuses the window, browser_use {browser:'chrome',install:true} installs Chrome from the official dmg, and taskbar identity/badging remains Windows-only.",   "homepage": "https://wiki.adom.inc/adom/adom-desktop",   "author": "Adom Inc.",
src/server.js+18−1
@@ -1519,6 +1519,7 @@ async function rescanProfile(browser, profileName, { adoptOrphans = false } = {}     }     session._lostBrowser = false;     delete session._disconnectedAt;+    delete session._healGaveUp;   // v1.9.305: a live tab is back — reset the give-up latch     attachTab(session, sessionId, page, { makeActive: false });     seenSessions.add(sessionId);     reattached++;@@ -1541,6 +1542,18 @@ async function rescanProfile(browser, profileName, { adoptOrphans = false } = {}   for (const [sid, s] of sessions.entries()) {     if (s.profileName === profileName) { try { await reconcileSessionTabs(s, sid); } catch (e) {} }   }+  // v1.9.305 (John: "we came back from sleep and lost all pup windows"): the fix for the reaper-vs-+  // recovery RACE. On wake, rescan reattached a page and cleared _lostBrowser=false, but the prune+  // above then dropped that page as the DEAD pre-sleep tab — leaving the session at 0 live tabs with+  // _lostBrowser already false. The 8s reaper's guard is `_lostBrowser && _disconnectedAt<120s`, so it+  // REAPED the recoverable session before the 20s healer could relaunch it (killed pup-dashboard ++  // noreply-bounce-admin; only hd-wiki-pages won the race). Re-mark any now-empty session as lost and+  // refresh its heal window, so the reaper leaves it alone and the healer brings it back at its URL.+  for (const [sid, s] of sessions.entries()) {+    if (s.profileName !== profileName) continue;+    const liveTabs = (s.tabs || []).filter(t => { try { return t && t.page && !t.page.isClosed(); } catch (e) { return false; } }).length;+    if (liveTabs === 0 && !s._healGaveUp) { s._lostBrowser = true; s._disconnectedAt = Date.now(); }+  }   // v1.9.243: repaint overlays IMMEDIATELY on reattach. A bridge restart wipes in-memory painted   // state while the REAL taskbar keeps whatever was on it — the dashboard and taskbar diverge until   // something repaints (John: "the taskbar icon you show for icon 1 is not consistent with what you@@ -5227,7 +5240,10 @@ async function reconcileModel(reason) {     // shared profile is still a dead session, and its lingering badge/identity is the "stale icon".     for (const [sid, s] of [...sessions]) {       if (_launchInFlight.has(s.profileName)) continue;-      if (s._lostBrowser && s._disconnectedAt && (Date.now() - s._disconnectedAt < 120000)) continue; // heal window+      // v1.9.305: !_healGaveUp — a session the healer is still trying to bring back (lost to sleep) is+      // NEVER reaped, even at 0 tabs, until the healer explicitly gives up. This is the reaper-vs-+      // recovery race fix (John lost his windows to sleep). rescanProfile keeps _disconnectedAt fresh.+      if (s._lostBrowser && !s._healGaveUp && s._disconnectedAt && (Date.now() - s._disconnectedAt < 120000)) continue; // heal window       let anyOpen = false;       for (const t of (s.tabs || [])) { try { if (t && t.page && !t.page.isClosed()) { anyOpen = true; break; } } catch (e) {} }       if (anyOpen) continue;@@ -13414,6 +13430,7 @@ server.listen(PORT, BIND_HOST, async () => {         if (now - rec.last < HEAL_COOLDOWN_MS) {           if (!rec.gaveUp) {             rec.gaveUp = true; _healRelaunch.set(sid, rec);+            try { sess._healGaveUp = true; } catch (e) {}   // v1.9.305: now the reaper may finally clean it up             console.log(`[heal] "${sid}" exceeded ${MAX_HEAL_ATTEMPTS} relaunch attempts — backing off ${Math.round(HEAL_COOLDOWN_MS / 60000)}m`);             recordCleanup(`pup tried to bring your "${sid}" window back but it kept failing — pup has stopped retrying for now. Reopen it manually when ready; your logins are safe on disk.`);           }