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.650 (insiders): vault migration keyed by the real service name; upgrade cycle waits for the old windows before relaunching
4 files changed
+27−21
rust/Cargo.lock+12−12@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-pup-bridge"-version = "2.0.649"+version = "2.0.650" dependencies = [ "pup-browser", "pup-cdp",@@ -655,7 +655,7 @@ dependencies = [ [[package]] name = "pup-browser"-version = "2.0.649"+version = "2.0.650" dependencies = [ "pup-core", "pup-host",@@ -667,7 +667,7 @@ dependencies = [ [[package]] name = "pup-cdp"-version = "2.0.649"+version = "2.0.650" dependencies = [ "serde_json", "thiserror",@@ -677,7 +677,7 @@ dependencies = [ [[package]] name = "pup-core"-version = "2.0.649"+version = "2.0.650" dependencies = [ "serde", "serde_json",@@ -687,7 +687,7 @@ dependencies = [ [[package]] name = "pup-dashboard"-version = "2.0.649"+version = "2.0.650" dependencies = [ "pup-cdp", "pup-core",@@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "pup-host"-version = "2.0.649"+version = "2.0.650" dependencies = [ "serde", "serde_json",@@ -709,14 +709,14 @@ dependencies = [ [[package]] name = "pup-host-mac"-version = "2.0.649"+version = "2.0.650" dependencies = [ "pup-host", ] [[package]] name = "pup-host-win"-version = "2.0.649"+version = "2.0.650" dependencies = [ "pup-core", "pup-host",@@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "pup-identity"-version = "2.0.649"+version = "2.0.650" dependencies = [ "fontdue", "image",@@ -736,7 +736,7 @@ dependencies = [ [[package]] name = "pup-session"-version = "2.0.649"+version = "2.0.650" dependencies = [ "pup-core", "serde",@@ -746,7 +746,7 @@ dependencies = [ [[package]] name = "pup-vault"-version = "2.0.649"+version = "2.0.650" dependencies = [ "pup-core", "pup-host",@@ -757,7 +757,7 @@ dependencies = [ [[package]] name = "pup-verbs"-version = "2.0.649"+version = "2.0.650" 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.649"+version = "2.0.650" edition = "2021" license = "MIT"
rust/host-win/src/lib.rs+7−7@@ -407,7 +407,7 @@ mod secrets { Some(v) } }- fn load(h: &WinHost) -> std::collections::BTreeMap<String, String> {+ 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(); } 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();@@ -419,11 +419,11 @@ mod secrets { 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!("adom-pup/{acct}");+ 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 moved > 0 { let _ = save(h, &m); eprintln!("[vault] moved {moved} login(s) from Windows Credential Manager into the DPAPI store and freed their slots"); }+ 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());@@ -453,23 +453,23 @@ mod secrets { impl HostSecrets for WinHost { fn secret_get(&self, service: &str, account: &str) -> Option<String> { let key = format!("{service}/{account}");- if let Some(v) = load(self).get(&key) { return Some(v.clone()); }+ if let Some(v) = load(self, service).get(&key) { return Some(v.clone()); } // a legacy entry the index did not name: move it too let v = cred_read(&key)?;- let mut m = load(self); m.insert(key.clone(), v.clone());+ let mut m = load(self, service); m.insert(key.clone(), v.clone()); if save(self, &m).is_ok() { cred_delete(&key); } Some(v) } fn secret_set(&self, service: &str, account: &str, secret: &str) -> Result<(), String> { let key = format!("{service}/{account}");- let mut m = load(self); m.insert(key.clone(), secret.to_string());+ let mut m = load(self, service); m.insert(key.clone(), secret.to_string()); save(self, &m)?; cred_delete(&key); // never keep a second copy in the capped store Ok(()) } fn secret_delete(&self, service: &str, account: &str) -> bool { let key = format!("{service}/{account}");- let mut m = load(self); let had = m.remove(&key).is_some();+ let mut m = load(self, service); let had = m.remove(&key).is_some(); let saved = save(self, &m).is_ok(); let legacy = cred_delete(&key); (had && saved) || legacy
rust/verbs/src/extdrive.rs+7−1@@ -109,8 +109,14 @@ pub fn reload_stale_worker(ctx: &Ctx) { 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);- pup_core::log(format!("[extdrive] lane worker {} is behind pup {}: re-staged the extension and cycled the lane browser ({n} process(es)); relaunching its {} window(s) from their files on the new extension", have.replace('-', "."), want.replace('-', "."), ids.len()));+ // 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+ // finished dying (AdomLapper, 2.0.649 upgrade: 5 windows, 4 lost).+ let t0 = std::time::Instant::now();+ while t0.elapsed() < Duration::from_secs(8) && hwnds.iter().any(|h| ctx.host.is_alive(*h)) { std::thread::sleep(Duration::from_millis(200)); }+ pup_core::log(format!("[extdrive] lane worker {} is behind pup {}: re-staged the extension and cycled the lane browser ({n} process(es), windows gone after {} ms); relaunching its {} window(s) from their files on the new extension", have.replace('-', "."), want.replace('-', "."), t0.elapsed().as_millis(), ids.len())); for f in files { crate::recovery::relaunch_from_file(ctx, &f); } }