← Commit history

v2.0.669 (insiders): Windows survive an upgrade: recovery no longer counts a window that is still on screen (waiting for the extension to reconnect) as a failed relaunch, so it never deletes a live window's record. Before, three quiet checks after a restart forgot the window, leaving it ownerless with no taskbar badge and making its thread open a duplicate. The identity audit also reads ab's nested reply now instead of reporting every window as unstamped.

John Lauer ·c4825126e7 ·1mo ago ·parent 7e0355b
4 files changed +30−20
rust/Cargo.lock+12−12
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"  [[package]] name = "adom-pup-bridge"-version = "2.0.668"+version = "2.0.669" dependencies = [  "pup-browser",  "pup-cdp",@@ -655,7 +655,7 @@ dependencies = [  [[package]] name = "pup-browser"-version = "2.0.668"+version = "2.0.669" dependencies = [  "pup-core",  "pup-host",@@ -667,7 +667,7 @@ dependencies = [  [[package]] name = "pup-cdp"-version = "2.0.668"+version = "2.0.669" dependencies = [  "serde_json",  "thiserror",@@ -677,7 +677,7 @@ dependencies = [  [[package]] name = "pup-core"-version = "2.0.668"+version = "2.0.669" dependencies = [  "serde",  "serde_json",@@ -687,7 +687,7 @@ dependencies = [  [[package]] name = "pup-dashboard"-version = "2.0.668"+version = "2.0.669" dependencies = [  "pup-cdp",  "pup-core",@@ -700,7 +700,7 @@ dependencies = [  [[package]] name = "pup-host"-version = "2.0.668"+version = "2.0.669" dependencies = [  "serde",  "serde_json",@@ -709,14 +709,14 @@ dependencies = [  [[package]] name = "pup-host-mac"-version = "2.0.668"+version = "2.0.669" dependencies = [  "pup-host", ]  [[package]] name = "pup-host-win"-version = "2.0.668"+version = "2.0.669" dependencies = [  "pup-core",  "pup-host",@@ -726,7 +726,7 @@ dependencies = [  [[package]] name = "pup-identity"-version = "2.0.668"+version = "2.0.669" dependencies = [  "fontdue",  "image",@@ -736,7 +736,7 @@ dependencies = [  [[package]] name = "pup-session"-version = "2.0.668"+version = "2.0.669" dependencies = [  "pup-core",  "serde",@@ -746,7 +746,7 @@ dependencies = [  [[package]] name = "pup-vault"-version = "2.0.668"+version = "2.0.669" dependencies = [  "pup-core",  "pup-host",@@ -757,7 +757,7 @@ dependencies = [  [[package]] name = "pup-verbs"-version = "2.0.668"+version = "2.0.669" dependencies = [  "base64",  "png 0.17.16",
rust/Cargo.toml+1−1
@@ -4,7 +4,7 @@ members = ["core", "host", "host-win", "host-mac", "cdp", "browser", "session",  # One version for the whole bridge, in lockstep with src/BRIDGE_VERSION (tools/ship.sh bumps both). [workspace.package]-version = "2.0.668"+version = "2.0.669" edition = "2021" license = "MIT" 
rust/verbs/src/extras.rs+7−1
@@ -171,7 +171,13 @@ pub fn start_identity_audit(ctx: Arc<Ctx>) {         if wins.is_empty() { continue; }         let caller = crate::identity::internal_caller();         let Ok(r) = ctx.ab.call("desktop_get_window_identity", json!({ "hwnds": wins.iter().map(|w| w.1).collect::<Vec<_>>() }), &caller, Duration::from_secs(10)) else { continue };-        let rows = r["rows"].as_array().or(r["windows"].as_array()).cloned().unwrap_or_default();+        // 2.0.669: ab answers {output:"{..., data:{windows:[...]}}"}; reading only the top level found no rows, so+        // every window was reported "the OS says None" every 5 minutes while Windows held the right identity+        // (AdomLapper, 2026-09-21). Accept every shape, and treat a reply with no rows as no reading at all.+        let body = match &r["output"] { Value::String(t) => serde_json::from_str::<Value>(t).unwrap_or(Value::Null), v if v.is_object() => v.clone(), _ => r.clone() };+        let pick = |v: &Value| v["rows"].as_array().or(v["windows"].as_array()).or(v["data"]["windows"].as_array()).or(v["data"]["rows"].as_array()).cloned();+        let rows = pick(&body).or_else(|| pick(&r)).unwrap_or_default();+        if rows.is_empty() { pup_core::log("[identity-audit] ab returned no rows; no reading this tick (not a mismatch)"); continue; }         let mut mismatched = Vec::new();         for (sid, hwnd, want) in wins {             let actual = rows.iter().find(|x| x["hwnd"].as_i64() == Some(hwnd)).and_then(|x| x["appId"].as_str().map(|s| s.to_string()));
rust/verbs/src/recovery.rs+10−6
@@ -113,15 +113,19 @@ pub fn relaunch_from_file(ctx: &Ctx, f: &Value) {     let url = f["url"].as_str().unwrap_or("about:blank").to_string();     let profile = f["profile"].as_str().unwrap_or("adom-you").to_string();     let owner = f["owner"].as_str().unwrap_or("").to_string();-    let attempts = { let mut m = RELAUNCHES.lock().unwrap(); match m.iter_mut().find(|(s, _)| *s == id) { Some(e) => { e.1 += 1; e.1 } None => { m.push((id.clone(), 1)); 1 } } };-    if attempts > 2 { pup_core::log(format!("[recovery] \"{id}\" failed to come back twice — giving up on it (the page itself may be crashing Chrome); removing its file")); ctx.sessions.delete_file(&id); crate::push_cleanup_report(format!("pup could NOT auto-recover the \"{id}\" window: it crashed again on relaunch ({url}). Open it again yourself with pup_open_window if you still need it.")); return; }+    // 2.0.669: a window still on glass is NOT a failed relaunch. The "alive, will re-adopt" path below used to+    // run after this counter, so three quiet 15 s ticks read as "failed to come back twice" and the file of a+    // LIVE window was deleted: after the 2.0.668 upgrade on AdomLapper (2026-09-20 23:57) three windows stayed on+    // screen with no session, no badge and no owner, and the adom/insiders thread opened a duplicate the next+    // morning. Re-adopt first, then decline while the window lives, and count only real relaunches.     if restore_cancelled(&id) { pup_core::log(format!("[recovery] \"{id}\": its restore was cancelled by a close; not relaunching")); ctx.sessions.delete_file(&id); return; }-    if readopt_ext(ctx, f) { return; }-    // 2.0.588: the window this file describes is still on glass: nothing is gone. Relaunching here replaced a-    // window the user had just raised. Re-adopt failed only because the worker was quiet; the next tick retries.+    if readopt_ext(ctx, f) { RELAUNCHES.lock().unwrap().retain(|(s, _)| *s != id); return; }     if let Some(h) = f["_hwnd"].as_i64().filter(|&h| h > 0) {-        if ctx.host.is_alive(h) { pup_core::log(format!("[recovery] \"{id}\": its window {h} is alive on glass; NOT relaunching (worker quiet, will re-adopt)")); return; }+        if ctx.host.is_alive(h) { pup_core::log(format!("[recovery] \"{id}\": its window {h} is alive on glass; NOT relaunching (worker quiet, will re-adopt on the next tick; its file is kept)")); return; }     }+    let attempts = { let mut m = RELAUNCHES.lock().unwrap(); match m.iter_mut().find(|(s, _)| *s == id) { Some(e) => { e.1 += 1; e.1 } None => { m.push((id.clone(), 1)); 1 } } };+    if attempts > 2 { pup_core::log(format!("[recovery] \"{id}\" failed to come back twice — giving up on it (the page itself may be crashing Chrome); removing its file")); ctx.sessions.delete_file(&id); crate::push_cleanup_report(format!("pup could NOT auto-recover the \"{id}\" window: it crashed again on relaunch ({url}). Open it again yourself with pup_open_window if you still need it.")); return; }+    // (2.0.588: a window still on glass is never relaunched; that check now runs before the attempt counter.)     pup_core::log(format!("[recovery] \"{id}\": its Chrome is gone (profile {profile}) — relaunching at {url} in the background (attempt {attempts})"));     let caller = pup_core::Caller { thread: owner.clone(), reason: "auto-recovery: the window's Chrome was gone".into(), ..Default::default() };     let open_args = json!({ "window": id, "url": url, "profile": profile, "takeover": true, "lane": f["transport"].as_str().map(|x| x.to_string()).unwrap_or_else(|| ctx.settings.get("defaultLane", "ext")), "owner": if owner.is_empty() { Value::Null } else { json!(owner) } });