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
2.0.408 on insiders: recovered tabs get their page scripts back; plan status
3 files changed
+15−4
CHANGELOG.md+1−1@@ -1,6 +1,6 @@ # Changelog — Puppeteer Bridge -## 2.0.407 — 2026-09-05 — pup as ONE Rust exe (spawn.kind exe): the Node runtime leaves the package+## 2.0.408 — 2026-09-05 — pup as ONE Rust exe (spawn.kind exe): the Node runtime leaves the package The bridge package is now a single sha256-verified Windows binary (`adom-pup-bridge.exe`, about 4.6 MB) plus its manifest: no Node, no npm, no postinstall, no 90 MB of node_modules, so a first
docs/PLAN-rust-port.md+4−3@@ -1,6 +1,6 @@ # pup in Rust: the port plan -**Status (2026-09-05, late night): SHIPPED TO INSIDERS as 2.0.407, the first Rust-exe build.** Phases 0+**Status (2026-09-05, late night): SHIPPED TO INSIDERS as 2.0.408, the first Rust-exe build (2.0.407 went first; 2.0.408 adds the recovered-tab script injection).** Phases 0 to 4 are done; the public tier stays on the Node build (2.0.396) until a Rust build is promoted on John's words. Written 2026-09-05 against pup 2.0.396; John approved the plan the same day ("lets get back to the plan").@@ -68,14 +68,15 @@ John's words. Written 2026-09-05 against pup 2.0.396; John approved the plan the builds the Rust package by default (`--shell node` keeps the Node path) and gained `--candidate` (everything except the insiders push; the manifest lands on the product page as adom-bridge-puppeteer-manifest-candidate.json for ONE desktop to install) and- `--insiders-from-candidate`. Seven candidates (2.0.401 to 2.0.407) went through ab's real+ `--insiders-from-candidate`. Eight candidates (2.0.401 to 2.0.408) went through ab's real `bridge_install` path on winvm before insiders saw one; each found something: ab's relay computes success from `success` and the CLI returns the parsed `output` alone (so every success now carries both); the Node-era Chrome had no DevToolsActivePort file (recovery now reads the session file's cdpPort); a window whose Chrome is gone must come back (relaunch from the file at boot and from the health loop, twice at most, with `_cleanupReport`); a killed exe could lose the port for a second (bind retry); panics now land in ~/.adom/bridge-logs/pup-rust-panic.log; the wiki's CSP- blocks inline scripts in a pup_render_html document (hint added). Measured on 2.0.407 through ab:+ blocks inline scripts in a pup_render_html document (hint added); a window recovered across an+ update had lost its owner chip, floaty and login capture (recovered tabs get them back). Measured on 2.0.408 through ab: `tools/parity.py --via-ab` 37 of 37, `tools/ab-smoke.sh` all pass (vault, cards, wiki auto-sign and view flip, annotate, recording, recorder HUD, download, dashboard, taskbar identity), exe kill then respawn with the window reattached, Chrome kill then every window relaunched, and
rust/verbs/src/recovery.rs+10@@ -79,6 +79,16 @@ fn recover_one(ctx: &Ctx, file: &Value, h: &BrowserHandle, taken: &mut Vec<Strin let hwnd = file["_hwnd"].as_i64().filter(|&h| h > 0 && ctx.host.is_alive(h)) .or_else(|| ctx.host.windows_for_pids(&[], Some("Chrome_WidgetWin_1")).into_iter().find(|w| ctx.host.has_tag(w.hwnd, &tag)).map(|w| w.hwnd)); if let Some(hh) = hwnd { s.hwnd = Some(hh); ctx.host.tag(hh, &tag); if !s.foreground { ctx.host.send_to_bottom_no_activate(hh); } }+ // A recovered tab is a fresh CDP session: give it what attach_tab gives a new one (the login+ // capture + floaty bindings, the owner chip, the floaty) or the window silently loses them+ // (measured 2026-09-05: no owner chip on a window recovered across the 2.0.407 update).+ if let Some(t) = s.tabs.last() {+ for name in ["__pupCredCapture", "__pupFloatyPush"] { let _ = h.cdp.send(Some(&t.cdp_session), "Runtime.addBinding", json!({ "name": name }), Duration::from_secs(5)); }+ let capture = crate::vault::capture_login_script();+ let _ = h.cdp.send(Some(&t.cdp_session), "Page.addScriptToEvaluateOnNewDocument", json!({ "source": capture }), Duration::from_secs(5));+ let _ = h.cdp.evaluate(&t.cdp_session, &capture, Duration::from_secs(5));+ crate::inject_page_scripts(ctx, &s, &h.cdp, &t.cdp_session);+ } Some(s) }