app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled on the taskbar with its AI thread's name.
← Commit history
v2.0.729 (insiders): pup notices when its browser has lost 3D graphics and fixes it. After its GPU process crashes a few times, Chrome stops using the GPU for the rest of that browser session and current Chrome has no software fallback, so every WebGL page (the wiki's 3D viewer) failed in pup with 'Cannot read properties of null (reading alpha)' (John, 2026-10-20). pup now tests WebGL in one of its windows every 5 minutes; after two failed tests in a row it restarts its browser at a quiet moment (no AI using a pup window for a minute, at most every 30 minutes), checks 3 minutes later that every saved window came back and restarts once more if not (pup-bridge#134), and the user gets a toast saying why. Proven on ConfRoomROG: GPU crashes turned WebGL off, the watchdog caught it on two probes, restarted the browser about 3.5 minutes later, WebGL came back and all 7 windows returned. Gates on arav-rog and ConfRoomROG: smoke all pass, foreground pass, jump list 11/14 (baseline), isolated-close replay kept every window, parity 48/56 (the window-verb gaps in pup-bridge#131). Includes 2.0.727.
3 files changed
+70−13
rust/Cargo.lock+12−12@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-pup-bridge"-version = "2.0.727"+version = "2.0.729" dependencies = [ "pup-browser", "pup-cdp",@@ -781,7 +781,7 @@ dependencies = [ [[package]] name = "pup-browser"-version = "2.0.727"+version = "2.0.729" dependencies = [ "pup-core", "pup-host",@@ -793,7 +793,7 @@ dependencies = [ [[package]] name = "pup-cdp"-version = "2.0.727"+version = "2.0.729" dependencies = [ "serde_json", "thiserror",@@ -803,7 +803,7 @@ dependencies = [ [[package]] name = "pup-core"-version = "2.0.727"+version = "2.0.729" dependencies = [ "serde", "serde_json",@@ -813,7 +813,7 @@ dependencies = [ [[package]] name = "pup-dashboard"-version = "2.0.727"+version = "2.0.729" dependencies = [ "pup-cdp", "pup-core",@@ -826,7 +826,7 @@ dependencies = [ [[package]] name = "pup-host"-version = "2.0.727"+version = "2.0.729" dependencies = [ "serde", "serde_json",@@ -835,7 +835,7 @@ dependencies = [ [[package]] name = "pup-host-mac"-version = "2.0.727"+version = "2.0.729" dependencies = [ "core-foundation", "core-graphics",@@ -851,7 +851,7 @@ dependencies = [ [[package]] name = "pup-host-win"-version = "2.0.727"+version = "2.0.729" dependencies = [ "pup-core", "pup-host",@@ -861,7 +861,7 @@ dependencies = [ [[package]] name = "pup-identity"-version = "2.0.727"+version = "2.0.729" dependencies = [ "fontdue", "image",@@ -871,7 +871,7 @@ dependencies = [ [[package]] name = "pup-session"-version = "2.0.727"+version = "2.0.729" dependencies = [ "pup-core", "serde",@@ -881,7 +881,7 @@ dependencies = [ [[package]] name = "pup-vault"-version = "2.0.727"+version = "2.0.729" dependencies = [ "pup-core", "pup-host",@@ -892,7 +892,7 @@ dependencies = [ [[package]] name = "pup-verbs"-version = "2.0.727"+version = "2.0.729" 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.727"+version = "2.0.729" edition = "2021" license = "MIT"
rust/verbs/src/recovery.rs+57@@ -643,11 +643,68 @@ pub fn sweep_idle_isolated(ctx: &Ctx) -> usize { n } +/// 2.0.728 (John, 2026-10-20: a wiki 3D viewer failed in pup with "Cannot read properties of null (reading 'alpha')"+/// while it worked in his own Chrome). After its GPU process crashes a few times, Chrome stops using the GPU for the+/// rest of that browser session, and current Chrome has no software WebGL fallback, so every 3D page breaks silently+/// until the browser restarts. Probe WebGL in a pup window every 5 min (every minute once a probe fails); after two+/// failures in a row, restart pup's browser when no AI has used a pup window for a minute (at most every 30 min), so+/// every window comes back with 3D working, and tell the user.+pub fn webgl_watch(ctx: &Ctx) {+ static STATE: std::sync::Mutex<(u64, u32, u64, u64)> = std::sync::Mutex::new((0, 0, 0, 0)); // last probe, failures, last restart, last wait log+ let now = pup_core::now_ms();+ let (last, fails, last_restart, _) = *STATE.lock().unwrap();+ let every = if fails > 0 { 60_000 } else { 300_000 };+ if now.saturating_sub(last) < every { return; }+ STATE.lock().unwrap().0 = now;+ let sessions: Vec<Session> = ctx.sessions.for_profile("adom-you").into_iter().filter(|s| crate::transport::is_ext(s)).collect();+ let probe = sessions.iter().find_map(|s| {+ let t = s.active().filter(|t| t.url.starts_with("http") || t.url.starts_with("file:"))?.clone();+ let lane = crate::transport::lane_for(ctx, s).ok()?;+ let expr = "(()=>{try{const c=document.createElement('canvas');const g=c.getContext('webgl2')||c.getContext('webgl');if(!g)return false;const l=g.getExtension('WEBGL_lose_context');if(l)l.loseContext();return true}catch(e){return null}})()";+ let r = crate::transport::cdp_raw(&lane, &t, "Runtime.evaluate", json!({ "expression": expr, "returnByValue": true }), 10).ok()?;+ r["result"]["value"].as_bool().map(|v| (v, s.id.clone()))+ });+ let Some((has_webgl, sid)) = probe else { return };+ if has_webgl { if fails > 0 { pup_core::log(format!("[webgl] back in \"{sid}\"")); } STATE.lock().unwrap().1 = 0; return; }+ let fails = { let mut g = STATE.lock().unwrap(); g.1 += 1; g.1 };+ pup_core::log(format!("[webgl] OFF in \"{sid}\" (probe {fails}): the browser has stopped giving pages 3D graphics, most likely after GPU-process crashes"));+ if fails < 2 { return; }+ if now.saturating_sub(last_restart) < 30 * 60_000 { return; }+ let busy_ms = sessions.iter().map(|s| s.last_agent_update_ms).max().unwrap_or(0);+ if now.saturating_sub(busy_ms) < 60_000 {+ let mut g = STATE.lock().unwrap();+ if now.saturating_sub(g.3) > 300_000 { g.3 = now; pup_core::log("[webgl] waiting for a quiet minute (an AI used a pup window in the last 60 s) before restarting the browser"); }+ return;+ }+ { let mut g = STATE.lock().unwrap(); g.1 = 0; g.2 = now; }+ let Some(a) = ctx.arc() else { return };+ std::thread::Builder::new().name("webgl-restart".into()).spawn(move || {+ let ctx2: &Ctx = &a;+ let caller = crate::identity::internal_caller();+ pup_core::log("[webgl] restarting pup's browser so 3D pages work again; every window comes back at its page");+ let r = crate::extras::restart_browser(ctx2, &caller, &json!({ "profile": "adom-you", "reason": "WebGL was off after GPU crashes; a fresh browser restores 3D" }));+ let mut ok = r["ok"] != json!(false) && r["success"] != json!(false);+ // The restore after a restart can stall on a busy box (ConfRoomROG, 2026-10-20: 3 of 8 windows back, the rest+ // left on disk while the extension went quiet; a second restart brought all of them). Check after 3 minutes and+ // restart once more if any saved window is still waiting.+ std::thread::sleep(Duration::from_secs(180));+ let live: std::collections::HashSet<String> = ctx2.sessions.all().iter().map(|s| s.id.clone()).collect();+ let waiting: Vec<String> = ctx2.sessions.read_files().iter().filter(|f| f["profile"].as_str().unwrap_or("adom-you") == "adom-you").filter_map(|f| f["sessionId"].as_str().map(|s| s.to_string())).filter(|id| !live.contains(id)).collect();+ if !waiting.is_empty() {+ pup_core::log(format!("[webgl] {} window(s) still waiting after the restart ({}); restarting once more to bring them back", waiting.len(), waiting.join(", ")));+ let r2 = crate::extras::restart_browser(ctx2, &caller, &json!({ "profile": "adom-you", "reason": "finish restoring windows after the WebGL restart" }));+ ok = r2["ok"] != json!(false) && r2["success"] != json!(false);+ }+ let _ = ctx2.ab.call("notify_user", json!({ "title": "pup restarted its browser", "body": if ok { "3D graphics (WebGL) had stopped working in pup's browser, so pages like the wiki's 3D viewer were failing. pup restarted it; your pup windows are back at the same pages." } else { "3D graphics (WebGL) stopped working in pup's browser and the automatic restart failed. Ask your AI to run pup_restart_browser." }, "focus": false }), &caller, Duration::from_secs(8));+ }).ok();+}+ pub fn health_tick(ctx: &Ctx) { sweep_idle_isolated(ctx); // 2.0.706: an isolated window's browser ends with its window crate::extdrive::adopt_tokenless(ctx); // 2.0.580: a token-less extension browser after a restart forget_user_closed(ctx); // 2.0.595: the user's close is final lane_gone_pass(ctx); // 2.0.595: a gone lane is forgotten unless it provably crashed+ webgl_watch(ctx); // 2.0.728: Chrome turned WebGL off after GPU crashes; restart when quiet crate::extdrive::rescue_birth_tabs(ctx); // 2.0.724: a window stuck on its birth page (PUPBIRTH-... on the taskbar) crate::extdrive::adopt_stray_windows(ctx); // 2.0.694: windows Chrome made outside pup (a dragged-out tab, a page's popup) crate::extdrive::rebind_unbound(ctx); // ext sessions whose birth outran the hwnd wait: bind + stamp now