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.651 (insiders): HOTFIX: vault migration deadlock (2.0.650); save before delete; upgrade cycle hwnds from live sessions
4 files changed
+28−21
rust/Cargo.lock+12−12@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-pup-bridge"-version = "2.0.650"+version = "2.0.651" dependencies = [ "pup-browser", "pup-cdp",@@ -655,7 +655,7 @@ dependencies = [ [[package]] name = "pup-browser"-version = "2.0.650"+version = "2.0.651" dependencies = [ "pup-core", "pup-host",@@ -667,7 +667,7 @@ dependencies = [ [[package]] name = "pup-cdp"-version = "2.0.650"+version = "2.0.651" dependencies = [ "serde_json", "thiserror",@@ -677,7 +677,7 @@ dependencies = [ [[package]] name = "pup-core"-version = "2.0.650"+version = "2.0.651" dependencies = [ "serde", "serde_json",@@ -687,7 +687,7 @@ dependencies = [ [[package]] name = "pup-dashboard"-version = "2.0.650"+version = "2.0.651" dependencies = [ "pup-cdp", "pup-core",@@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "pup-host"-version = "2.0.650"+version = "2.0.651" dependencies = [ "serde", "serde_json",@@ -709,14 +709,14 @@ dependencies = [ [[package]] name = "pup-host-mac"-version = "2.0.650"+version = "2.0.651" dependencies = [ "pup-host", ] [[package]] name = "pup-host-win"-version = "2.0.650"+version = "2.0.651" dependencies = [ "pup-core", "pup-host",@@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "pup-identity"-version = "2.0.650"+version = "2.0.651" dependencies = [ "fontdue", "image",@@ -736,7 +736,7 @@ dependencies = [ [[package]] name = "pup-session"-version = "2.0.650"+version = "2.0.651" dependencies = [ "pup-core", "serde",@@ -746,7 +746,7 @@ dependencies = [ [[package]] name = "pup-vault"-version = "2.0.650"+version = "2.0.651" dependencies = [ "pup-core", "pup-host",@@ -757,7 +757,7 @@ dependencies = [ [[package]] name = "pup-verbs"-version = "2.0.650"+version = "2.0.651" 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.650"+version = "2.0.651" edition = "2021" license = "MIT"
rust/host-win/src/lib.rs+14−7@@ -408,25 +408,32 @@ mod secrets { } } fn load(h: &WinHost, service: &str) -> std::collections::BTreeMap<String, String> {- let mut g = CACHE.lock().unwrap_or_else(|e| e.into_inner());- if let Some(m) = g.as_ref() { return m.clone(); }+ if let Some(m) = CACHE.lock().unwrap_or_else(|e| e.into_inner()).as_ref() { return m.clone(); }+ // NOT under the cache lock: 2.0.650 held it here and called save(), which locks it again; every vault+ // verb on the laptop hung and the migration deleted 30 entries it had not yet persisted. let mut m: std::collections::BTreeMap<String, String> = std::fs::read(store_path(h)).ok().and_then(|b| dpapi(&b, false)).and_then(|j| serde_json::from_slice(&j).ok()).unwrap_or_default();- // one-time migration: every index entry still in Credential Manager moves here and its slot is freed+ // one-time migration, SAVE FIRST: read every legacy entry into the map, persist the whole map, and only+ // then delete the Credential Manager copies (a failed save leaves the legacy store untouched). let index = h.state_dir().join("pup-credentials.index.json"); if let Ok(txt) = std::fs::read_to_string(&index) { if let Ok(v) = serde_json::from_str::<serde_json::Value>(&txt) {- let mut moved = 0usize;+ let mut moved: Vec<String> = Vec::new(); for e in v["entries"].as_array().cloned().unwrap_or_default() { let acct = e["keytarAccount"].as_str().or(e["host"].as_str()).unwrap_or("").to_string(); if acct.is_empty() { continue; } let key = format!("{service}/{acct}"); if m.contains_key(&key) { continue; }- if let Some(secret) = cred_read(&key) { m.insert(key.clone(), secret); cred_delete(&key); moved += 1; }+ if let Some(secret) = cred_read(&key) { m.insert(key.clone(), secret); moved.push(key); }+ }+ if !moved.is_empty() {+ match save(h, &m) {+ Ok(()) => { for k in &moved { cred_delete(k); } pup_core::log(format!("[vault] moved {} login(s) from Windows Credential Manager into the DPAPI store and freed their slots", moved.len())); }+ Err(e) => pup_core::log(format!("[vault] migration NOT applied (save failed: {e}); the Credential Manager copies were left in place")),+ } }- if moved > 0 { let _ = save(h, &m); pup_core::log(format!("[vault] moved {moved} login(s) from Windows Credential Manager into the DPAPI store and freed their slots")); } } }- *g = Some(m.clone());+ *CACHE.lock().unwrap_or_else(|e| e.into_inner()) = Some(m.clone()); m } fn save(h: &WinHost, m: &std::collections::BTreeMap<String, String>) -> Result<(), String> {
rust/verbs/src/extdrive.rs+1−1@@ -105,11 +105,11 @@ pub fn reload_stale_worker(ctx: &Ctx) { // recovery uses to tell pup's own kill from the user closing the last window (AdomLapper, 2026-09-19). This is // the crash-restore path, driven on purpose: reserve every extension window, end the lane, relaunch each // window from its file on the re-staged extension, in the background, at its last page.+ let hwnds: Vec<i64> = ctx.sessions.all().into_iter().filter(|s| s.transport == "ext").filter_map(|s| s.hwnd).collect(); // 2.0.651: from the live sessions (the file has no hwnd) let files: Vec<Value> = ctx.sessions.all().into_iter().filter(|s| s.transport == "ext").map(|s| s.file_json()).collect(); let ids: Vec<String> = files.iter().filter_map(|f| f["sessionId"].as_str().map(|s| s.to_string())).collect(); crate::recovery::reserve(&ids); for id in &ids { ctx.sessions.remove(id); }- let hwnds: Vec<i64> = files.iter().filter_map(|f| f["hwnd"].as_i64()).collect(); let n = crate::kill_extdrive(ctx); // 2.0.650: WAIT for the windows to be gone before relaunching. The kill is asynchronous; relaunch_from_file saw // every window still "alive on glass", declined ("will re-adopt"), and the sessions were lost when the browser