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.723 (insiders): Closing an isolated pup window can no longer end the main browser. A health sweep dropped the isolated lane while its close was still waiting, the cleanup then fell back to the shared adom-you lane, wiped its record, and the next open ended the live browser as an orphan, taking every pup window with it (John's laptop, 2026-10-15). The lane is now fixed for the whole cleanup, a missing lane is never replaced by adom-you, and a browser that is polling is adopted instead of ended. Replayed on arav-rog and ConfRoomROG (main window open, isolated window opened and closed): every window survived. Gates: smoke all pass, foreground pass, parity 55/56, jump list 11/14 (baseline). Includes 2.0.722.
4 files changed
+53−21
rust/Cargo.lock+12−12@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-pup-bridge"-version = "2.0.722"+version = "2.0.723" dependencies = [ "pup-browser", "pup-cdp",@@ -781,7 +781,7 @@ dependencies = [ [[package]] name = "pup-browser"-version = "2.0.722"+version = "2.0.723" dependencies = [ "pup-core", "pup-host",@@ -793,7 +793,7 @@ dependencies = [ [[package]] name = "pup-cdp"-version = "2.0.722"+version = "2.0.723" dependencies = [ "serde_json", "thiserror",@@ -803,7 +803,7 @@ dependencies = [ [[package]] name = "pup-core"-version = "2.0.722"+version = "2.0.723" dependencies = [ "serde", "serde_json",@@ -813,7 +813,7 @@ dependencies = [ [[package]] name = "pup-dashboard"-version = "2.0.722"+version = "2.0.723" dependencies = [ "pup-cdp", "pup-core",@@ -826,7 +826,7 @@ dependencies = [ [[package]] name = "pup-host"-version = "2.0.722"+version = "2.0.723" dependencies = [ "serde", "serde_json",@@ -835,7 +835,7 @@ dependencies = [ [[package]] name = "pup-host-mac"-version = "2.0.722"+version = "2.0.723" dependencies = [ "core-foundation", "core-graphics",@@ -851,7 +851,7 @@ dependencies = [ [[package]] name = "pup-host-win"-version = "2.0.722"+version = "2.0.723" dependencies = [ "pup-core", "pup-host",@@ -861,7 +861,7 @@ dependencies = [ [[package]] name = "pup-identity"-version = "2.0.722"+version = "2.0.723" dependencies = [ "fontdue", "image",@@ -871,7 +871,7 @@ dependencies = [ [[package]] name = "pup-session"-version = "2.0.722"+version = "2.0.723" dependencies = [ "pup-core", "serde",@@ -881,7 +881,7 @@ dependencies = [ [[package]] name = "pup-vault"-version = "2.0.722"+version = "2.0.723" dependencies = [ "pup-core", "pup-host",@@ -892,7 +892,7 @@ dependencies = [ [[package]] name = "pup-verbs"-version = "2.0.722"+version = "2.0.723" 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.722"+version = "2.0.723" edition = "2021" license = "MIT"
rust/verbs/src/extdrive.rs+17−1@@ -54,7 +54,9 @@ pub fn default_lane() -> std::sync::Arc<LaneState> { DEFAULT_LANE.get_or_init(|| pub fn st() -> std::sync::Arc<LaneState> { let p = CUR_PROFILE.with(|c| c.borrow().clone()); if p.is_empty() || p == "adom-you" { return default_lane(); }- lane_for_profile(&p).unwrap_or_else(default_lane)+ // 2.0.723: never fall back to the adom-you lane for another profile. A lane dropped mid-call (the idle sweep of+ // an isolated profile) made a close clear the adom-you lane and end the user's main browser.+ ensure_lane(&p) } pub fn cur_profile() -> String { let p = CUR_PROFILE.with(|c| c.borrow().clone()); if p.is_empty() { "adom-you".into() } else { p } } pub fn enter_profile(profile: &str) {@@ -907,6 +909,20 @@ pub fn ext_handle() -> std::sync::Arc<crate::BrowserHandle> { #[cfg(test)] mod tests {+ // 2.0.723: a lane dropped while its thread still works in it (the idle sweep during a 20 s close) must never+ // resolve to the adom-you lane; that wiped the user's main browser record and ended it.+ #[test] fn dropped_lane_never_aliases_adom_you() {+ super::default_lane().token.lock().unwrap().clear();+ *super::default_lane().token.lock().unwrap() = "keep-me".into();+ super::enter_profile("iso-test-723");+ super::drop_lane_for_profile("iso-test-723");+ let l = super::st();+ assert_eq!(l.profile, "iso-test-723");+ l.token.lock().unwrap().clear();+ assert_eq!(*super::default_lane().token.lock().unwrap(), "keep-me");+ super::leave_profile();+ super::drop_lane_for_profile("iso-test-723");+ } #[test] fn blank_or_birth() { assert!(super::is_blank_or_birth("")); assert!(super::is_blank_or_birth("about:blank")); assert!(super::is_blank_or_birth("chrome-extension://liomcaladmooagjiebkfgegciadapnpi/birth.html?token=b1y2"));
rust/verbs/src/lib.rs+23−7@@ -120,15 +120,21 @@ pub fn wait_profile_free(ctx: &Ctx, profile: &str, max: Duration) -> bool { } pub fn kill_extdrive(ctx: &Ctx) -> u32 {- let pids: Vec<u32> = extdrive::st().pids.lock().unwrap().clone();+ // 2.0.723 (John's laptop, 2026-10-15: every pup window vanished). The lane is chosen ONCE. Closing an isolated+ // window waited 20 s for its profile to free up, the health sweep dropped that lane meanwhile, and the clears+ // below then resolved st() to the adom-you lane: its token and pids were wiped, and the next open ended the+ // user's live adom-you browser as an "orphan" (18 processes).+ let lane = extdrive::st();+ let lane_path = lane_file(ctx);+ let pids: Vec<u32> = lane.pids.lock().unwrap().clone(); let mut n = 0; for p in pids { if ctx.host.alive(p) { ctx.host.kill(p); n += 1; } }- if n > 0 { let _ = wait_profile_free(ctx, &extdrive::cur_profile(), Duration::from_secs(20)); }- extdrive::st().pids.lock().unwrap().clear();- extdrive::st().token.lock().unwrap().clear();- extdrive::mark_dead();- extdrive::reset_worker_version(); // 2.0.647: the next hello is the truth, not the last lane's- let _ = std::fs::remove_file(lane_file(ctx));+ if n > 0 { let _ = wait_profile_free(ctx, &lane.profile, Duration::from_secs(20)); }+ lane.pids.lock().unwrap().clear();+ lane.token.lock().unwrap().clear();+ *lane.last_poll.lock().unwrap() = 0;+ lane.worker.lock().unwrap().clear(); // 2.0.647: the next hello is the truth, not the last lane's+ let _ = std::fs::remove_file(lane_path); if n > 0 { pup_core::log(format!("[extdrive] closed {n} browser process(es)")); } n }@@ -299,6 +305,16 @@ pub fn launch_extdrive(ctx: &Ctx, url: &str) -> Result<u32, Value> { while t0.elapsed() < Duration::from_secs(10) { if !lock.exists() { break; } std::thread::sleep(Duration::from_millis(200)); } } }+ // 2.0.723: a browser on this profile that polled the lane in the last 10 s is ALIVE and connected, whatever the+ // pid list says (it can be emptied by a lost lane record). Adopt it; never end it as stale or orphaned.+ if extdrive::connected_ms_ago() < 10_000 {+ let live = orphan_browser_pids(ctx, &dir);+ if !live.is_empty() {+ set_extdrive_pids(live.clone()); persist_lane(ctx);+ pup_core::log(format!("[lane] profile \"{profile}\": its browser is connected ({} process(es)) but pup had no pids for it; adopted, not relaunched", live.len()));+ return Ok(live[0]);+ }+ } let stale = kill_extdrive(ctx); // 2.0.686: kill_extdrive now waits until the profile is truly free if stale > 0 { pup_core::log(format!("[extdrive] ended {stale} stale browser process(es) holding profile \"{profile}\" before relaunching"));