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.702 (insiders): The toolbar pen works on pages that change their address in place (Slack, Gmail, YouTube): it finds its window by the browser's tab id, not by the page address pup last recorded, and falls back to the pup window in front. A pen click that still cannot start shows a ! on the button with the reason, and pup logs it (John's Slack window, 2026-10-07: 'it didn't ever show me the toolbar'). Gates on winvm: smoke all pass, parity 55/56, jump list 12/15; foreground proof passed 3 of 4 runs, one run had 1 of 20 samples with a pup window on top, not reproduced.
7 files changed
+40−20
rust/Cargo.lock+12−12@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-pup-bridge"-version = "2.0.701"+version = "2.0.702" dependencies = [ "pup-browser", "pup-cdp",@@ -655,7 +655,7 @@ dependencies = [ [[package]] name = "pup-browser"-version = "2.0.701"+version = "2.0.702" dependencies = [ "pup-core", "pup-host",@@ -667,7 +667,7 @@ dependencies = [ [[package]] name = "pup-cdp"-version = "2.0.701"+version = "2.0.702" dependencies = [ "serde_json", "thiserror",@@ -677,7 +677,7 @@ dependencies = [ [[package]] name = "pup-core"-version = "2.0.701"+version = "2.0.702" dependencies = [ "serde", "serde_json",@@ -687,7 +687,7 @@ dependencies = [ [[package]] name = "pup-dashboard"-version = "2.0.701"+version = "2.0.702" dependencies = [ "pup-cdp", "pup-core",@@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "pup-host"-version = "2.0.701"+version = "2.0.702" dependencies = [ "serde", "serde_json",@@ -709,14 +709,14 @@ dependencies = [ [[package]] name = "pup-host-mac"-version = "2.0.701"+version = "2.0.702" dependencies = [ "pup-host", ] [[package]] name = "pup-host-win"-version = "2.0.701"+version = "2.0.702" dependencies = [ "pup-core", "pup-host",@@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "pup-identity"-version = "2.0.701"+version = "2.0.702" dependencies = [ "fontdue", "image",@@ -736,7 +736,7 @@ dependencies = [ [[package]] name = "pup-session"-version = "2.0.701"+version = "2.0.702" dependencies = [ "pup-core", "serde",@@ -746,7 +746,7 @@ dependencies = [ [[package]] name = "pup-vault"-version = "2.0.701"+version = "2.0.702" dependencies = [ "pup-core", "pup-host",@@ -757,7 +757,7 @@ dependencies = [ [[package]] name = "pup-verbs"-version = "2.0.701"+version = "2.0.702" 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.701"+version = "2.0.702" edition = "2021" license = "MIT"
rust/verbs/src/annotate.rs+2−1@@ -20,7 +20,8 @@ pub fn floaty_script(ctx: &Ctx) -> String { /// pup_annotate {tool: pen|snip, mode: auto|multi}: arm the overlay and watch for the capture. pub fn start(ctx: &Ctx, caller: &Caller, args: &Value) -> Value {- let s = match crate::resolve(ctx, caller, args) { Ok(s) => s, Err(e) => return e };+ // 2.0.702: say why in the log. A toolbar click that resolved no window used to leave no trace at all.+ let s = match crate::resolve(ctx, caller, args) { Ok(s) => s, Err(e) => { pup_core::log(format!("[annotate] could not start (by {}): {} (tab {:?}, url {:?})", caller.thread, e["error"].as_str().unwrap_or("?"), args.get("_tabId"), args.get("_tabUrl").and_then(|u| u.as_str()).map(|u| u.chars().take(80).collect::<String>()))); return e } }; let Some(t) = s.active().cloned() else { return err("no_tab", "the window has no active tab", None) }; let lane = match crate::transport::lane_for(ctx, &s) { Ok(l) => l, Err(e) => return e }; let tool = args["tool"].as_str().unwrap_or("pen").to_string();
rust/verbs/src/lib.rs+11@@ -851,7 +851,18 @@ pub fn resolve(ctx: &Ctx, caller: &Caller, args: &Value) -> Result<Session, Valu // 2.0.453 (John, woke from sleep, toolbar pen did nothing): the Adom toolbar identifies its window by // the TAB URL (`_tabUrl`, Node's contract); the Rust port never resolved it, so a pen click landed // here with thread "user-toolbar" and no window. Any tab in any pup window matches, exact first.+ // 2.0.702: the browser's own tab id first. A page that changes its address in place (Slack, Gmail, YouTube)+ // no longer matched pup's recorded URL, so the toolbar pen resolved no window and did nothing (John's Slack+ // window, 2026-10-07). The id never changes.+ if let Some(tid) = args.get("_tabId").and_then(|v| v.as_i64()).map(|v| v.to_string()) {+ if let Some(s) = ctx.sessions.all().into_iter().find(|s| s.tabs.iter().any(|t| t.target_id == tid)) { return Ok(s); }+ } if let Some((s, _)) = args.get("_tabUrl").and_then(|u| u.as_str()).and_then(|u| session_for_tab_url(ctx, u)) { return Ok(s); }+ // last resort for a human click: the pup window in front is the one they clicked in+ if args.get("_tabUrl").is_some() || args.get("_tabId").is_some() {+ let fg = ctx.host.foreground();+ if fg != 0 { if let Some(s) = ctx.sessions.all().into_iter().find(|s| s.hwnd == Some(fg)) { return Ok(s); } }+ } let d = thread_default_session_id(&caller.thread); if ctx.sessions.exists(&d) { d } else { let mine = ctx.sessions.owned_by(&caller.thread);
src/extension-annotate/go.js+10−2@@ -8,11 +8,19 @@ const r = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(1200) }); const j = await r.json(); if (!j || j.bridge !== 'puppeteer') continue;- await fetch(`http://127.0.0.1:${port}/command`, {+ const res = await fetch(`http://127.0.0.1:${port}/command`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-adom-caller-thread': 'user-toolbar', 'x-adom-caller-reason': 'Adom Annotate button' },- body: JSON.stringify({ command: 'pup_annotate', args: { _tabUrl: tab && tab.url, tool: 'pen', caller: { aiThread: 'user-toolbar' } } }),+ body: JSON.stringify({ command: 'pup_annotate', args: { _tabUrl: tab && tab.url, _tabId: tab && tab.id, tool: 'pen', caller: { aiThread: 'user-toolbar' } } }), });+ // 2.0.702: a refusal used to vanish with the popup, so a click that did nothing said nothing. Put the reason on the+ // button: a "!" badge and the error in its tooltip.+ try {+ const j = await res.json();+ const okd = j && j.ok !== false && !(j.error && String(j.error).length);+ chrome.action.setBadgeText({ text: okd ? '' : '!' });+ chrome.action.setTitle({ title: okd ? 'Annotate this page (Alt+Shift+A)' : 'Annotate could not start: ' + String(j.error || j.errorCode || 'unknown').slice(0, 180) });+ } catch (e) {} break; } catch (e) {} }
src/extension/popup.js+1−1@@ -72,7 +72,7 @@ async function cmd(port, command, args) { if (!btn) return; const act = btn.dataset.act; try {- const base = sid ? { sessionId: sid } : { _tabUrl: tab && tab.url };+ const base = sid ? { sessionId: sid } : { _tabUrl: tab && tab.url, _tabId: tab && tab.id }; let r = null; if (act === 'annotate') r = await cmd(port, 'pup_annotate', { ...base }); else if (act === 'snip') r = await cmd(port, 'pup_annotate', { ...base, tool: 'snip' });
src/extension/worker.js+3−3@@ -131,7 +131,7 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { await fetch(`http://127.0.0.1:${found.port}/command`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-adom-caller-thread': 'user-toolbar', 'x-adom-caller-reason': 'floaty pencil click' },- body: JSON.stringify({ command: 'pup_annotate', args: { _tabUrl: sender.tab.url, tool: msg.tool || 'pen', caller: { aiThread: 'user-toolbar' } } }),+ body: JSON.stringify({ command: 'pup_annotate', args: { _tabUrl: sender.tab.url, _tabId: sender.tab.id, tool: msg.tool || 'pen', caller: { aiThread: 'user-toolbar' } } }), }).catch(() => {}); sendResponse({ ok: true }); }@@ -163,8 +163,8 @@ chrome.commands.onCommand.addListener(async (command) => { // v0.2.7: Alt+Shift+V toggles the wiki view — pup resolves the current view server-side // (view:"toggle"), so the hotkey needs no state. const payload = command === 'pup-view'- ? { command: 'pup_wiki_set_view', args: { _tabUrl: tab.url, view: 'toggle', caller: { aiThread: 'user-toolbar' } } }- : { command: 'pup_annotate', args: { _tabUrl: tab.url, tool: 'pen', caller: { aiThread: 'user-toolbar' } } };+ ? { command: 'pup_wiki_set_view', args: { _tabUrl: tab.url, _tabId: tab.id, view: 'toggle', caller: { aiThread: 'user-toolbar' } } }+ : { command: 'pup_annotate', args: { _tabUrl: tab.url, _tabId: tab.id, tool: 'pen', caller: { aiThread: 'user-toolbar' } } }; await fetch(`http://127.0.0.1:${found.port}/command`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-adom-caller-thread': 'user-toolbar', 'x-adom-caller-reason': command === 'pup-view' ? 'Alt+Shift+V view-toggle hotkey' : 'Alt+Shift+A annotate hotkey' },