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
Recovered windows finally re-resolve their taskbar identity, read-only. The mimic ralph proved every window went bare after a respawn: the persisted-handle continuity check was silent, geometry cannot tell identical parked windows apart, and the off-screen re-capture failed silently on this box because Chrome clamps off-screen coordinates so the absolute-position match never hit (and its restore dumped windows at 0,0, itself visible wonk). Four changes: enum rows now carry the native window title; a new natural-title resolver matches each session's active-tab title against pup-owned windows only (pid-fenced so the user's own Chrome can never match, exactly-one or refuse, nothing written anywhere); the re-capture identifies the moved window by bounds change instead of absolute position (clamp-proof) and restores the exact original rect; and the continuity/recovery paths now log their outcomes so silent success or failure is visible
4 files changed
+57−13
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "2.0.43",- "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.43/adom-bridge-puppeteer-v2.0.43.zip",- "sha256": "cca60c0bdf6b2e5417cdc9b84c1d979fc0e3d827dfaed13cba61f8456cdff7fa",- "size": 646715,+ "version": "2.0.44",+ "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.44/adom-bridge-puppeteer-v2.0.44.zip",+ "sha256": "adc37c336215ea9549cf8a1579e7495b803bca04469ae756511c1dd991586c9a",+ "size": 647897, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-2.0.43+2.0.44
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "2.0.43",+ "version": "2.0.44", "description": "Drives a browser ALREADY on the machine \u2014 installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) \u2014 launched with a FRESH isolated profile, so the common case has NO ~150 MB Chrome-for-Testing download and it never touches the user's real tabs/logins. Binds its local server to LOOPBACK (127.0.0.1) only, so no Windows Firewall 'allow Node through the firewall' prompt ever pops. Chrome for Testing is fetched only as a LAST RESORT if the machine has no Chromium browser at all. SPAWN-VERIFIED fallback: pup tries each detected browser (Chrome \u2192 Edge \u2192 CfT) and actually launches it, falling through to the next if one won't start (and handling browsers like Edge that relaunch onto another PID), so a corrupt/partial install never hard-fails an open. The browser that launches is cached as the default (persisted across restarts) so it isn't re-probed each time. On spawn the bridge SELF-WARMS: detects browsers + disk, and only if the box has no Chrome/Edge and no cached CfT does it background-fetch CfT so the first open is instant. pup_use pins a specific browser (chrome | edge | cft | auto); {browser:'chrome',install:true} downloads & installs REAL Google Chrome (user-scoped, no admin) on an Edge-only box. pup_readiness (READ-ONLY) reports which browser will be used + the full candidate list + the default + free disk. Every failure returns a specific errorCode + _hint (chrome_install_no_disk, chrome_download_network, chrome_install_permission, chrome_stable_not_detected, \u2026). Launches with FULL browser permissions by default for full debug (strictPermissions:true opts out). Headful automation: open/close windows+tabs, navigate, screenshot, eval JS, record. pup is a GENERIC, non-profile browser only \u2014 every session is a fresh isolated Chrome/Edge process, never the user's real signed-in browser; profile defaults to sessionId. To drive the user's REAL Chrome/Edge (their profile, cookies, SSO), use the Adom browser EXTENSION's nbrowser_* verbs \u2014 a separate bridge, not pup's job. macOS (v1.9.74+): full parity with mac-native mechanics \u2014 background-by-default is the app-level hide (windows stay drivable/screenshottable while hidden), raise/lower map to unhide+activate/hide, pup_alert_window posts a native toast whose click focuses the window, pup_use {browser:'chrome',install:true} installs Chrome from the official dmg, and taskbar identity/badging remains Windows-only.", "homepage": "https://wiki.adom.inc/adom/adom-bridge", "author": "Adom Inc.",
src/server.js+51−7@@ -1983,6 +1983,7 @@ async function recoverSessions() { // the persisted Chrome pid" ties the handle to the exact process it was born under. Record // the pid; hwndBelongsToPup accepts that pair as proof (and learns the pid for siblings). if (pid) session._recoveredHwndPid = Number(pid) || null;+ console.log(`[recovery] "${sessionId}" restored persisted hwnd ${sf._hwnd} (continuity proof pid ${pid || 'none'})`); } // v1.9.202 (John's icon audit): a recovered window came back with NO taskbar badge. Under // minimalTouch the overlay is applied only on OPEN and NAVIGATE, and the re-stamp sweeps that@@ -5559,7 +5560,7 @@ async function enumChromeWidgetWindows() { if (w.visible === false) continue; const r = w.rect || []; const h = Number(w.hwnd);- if (h && Number.isFinite(Number(r[2]))) rows.push({ h, pid: Number(w.pid), L: Number(r[0]), T: Number(r[1]), W: Number(r[2]) - Number(r[0]), H: Number(r[3]) - Number(r[1]) });+ if (h && Number.isFinite(Number(r[2]))) rows.push({ h, pid: Number(w.pid), L: Number(r[0]), T: Number(r[1]), W: Number(r[2]) - Number(r[0]), H: Number(r[3]) - Number(r[1]), title: String(w.title || '') }); } return rows; } catch (e) { return []; }@@ -5597,7 +5598,7 @@ async function resolveSessionHwndByBounds(session, sessionId) { // v1.9.278: NEVER trust a cached hwnd blind - validate it still belongs to pup before reuse // (a dead/recycled handle now owned by Edge is exactly how the Edge overlay happened). if (session._hwnd) {- if (await hwndBelongsToPup(session._hwnd, session)) { session._hwndFailStreak = 0; session._hwndBackoffUntil = 0; return Number(session._hwnd); }+ if (await hwndBelongsToPup(session._hwnd, session)) { session._hwndFailStreak = 0; session._hwndBackoffUntil = 0; console.log(`[hwnd] "${sessionId}" cached/persisted handle ${session._hwnd} VALIDATED (continuity)`); return Number(session._hwnd); } console.log(`[hwnd] "${sessionId}" cached hwnd ${session._hwnd} is stale/foreign — discarding and re-resolving`); session._hwnd = null; }@@ -5640,6 +5641,37 @@ async function resolveSessionHwndByBounds(session, sessionId) { const b = fw && (fw.best || (fw.data && fw.data.best)); if (cnt === 1 && b && b.hwnd) { session._hwnd = Number(b.hwnd); persistSessionHwnd(sessionId, Number(b.hwnd)); console.log(`[hwnd] "${sessionId}" resolved by unique TITLE tag -> ${b.hwnd}`); return Number(b.hwnd); } } catch (e) {}+ // v2.0.44 NATURAL-TITLE RESOLUTION (read-only, no tag, no geometry): the OS window title IS the+ // active tab's page title (Chrome mirrors it), and pup already knows each session's active tab.+ // Match this session's active-tab title against the native rows — exactly-one match on a+ // pup-owned pid is proof. This restores the v1.9.274 title-first doctrine WITHOUT the title tag+ // that 2.0.24 removed as user-facing mangling: nothing is written anywhere, we just read what+ // Chrome already shows. PID FENCE: candidate rows must belong to this profile's tracked pids or+ // the recovery-continuity pid — John's own Chrome (same window class!) can never be matched even+ // if he has the identical page open. Same-title siblings yield 2+ matches and are refused+ // (bare > wrong). This is what finally re-resolves recovered windows the geometry never could.+ try {+ const _at = session.activeTabId && (session.tabs || []).find(t => t.tabId === session.activeTabId);+ const _pg0 = (_at && _at.page) || ((session.tabs || [])[0] && (session.tabs || [])[0].page);+ let _ptitle = '';+ try { _ptitle = String(await Promise.race([_pg0.title(), new Promise(r => setTimeout(() => r(''), 2500))]) || '').trim(); } catch (e) {}+ if (_ptitle.length >= 5) {+ const _rows0 = await enumChromeWidgetWindows();+ const _profPids0 = pupBrowserPidSet(session.profileName);+ const _okPid = (p) => _profPids0.has(p) || (session._recoveredHwndPid && Number(p) === Number(session._recoveredHwndPid));+ const _tm = _rows0.filter(r => r.title && _okPid(r.pid) && !hwndOwnedByOtherSession(r.h, sessionId)+ && (r.title === _ptitle || r.title.startsWith(_ptitle + ' - ') || r.title.startsWith(_ptitle + ' \u2013 ')));+ if (_tm.length === 1) {+ const got = Number(_tm[0].h);+ session._hwnd = got; persistSessionHwnd(sessionId, got);+ session._hwndFailStreak = 0; session._hwndBackoffUntil = 0;+ try { const p0b = (session.tabs || [])[0]; if (p0b && p0b.page) p0b.page.__pupHwnd = got; } catch (e) {}+ console.log(`[hwnd] "${sessionId}" resolved OS handle ${got} — NATURAL-title match ("${_ptitle.slice(0, 60)}")`);+ return got;+ }+ if (_tm.length > 1) console.log(`[hwnd] "${sessionId}" natural-title "${_ptitle.slice(0, 40)}" matches ${_tm.length} windows — ambiguous, falling through`);+ }+ } catch (e) {} const page = (session.tabs || [])[0] && (session.tabs || [])[0].page; if (!page) return null; // v1.9.290 UNIQUE-WIDTH WIGGLE: parked windows share the identical rect, so plain geometry can't@@ -5729,16 +5761,28 @@ async function resolveSessionHwndByBounds(session, sessionId) { const { windowId } = await cdp2.send('Browser.getWindowForTarget'); const gb = await cdp2.send('Browser.getWindowBounds', { windowId }); const orig = gb && gb.bounds;- const probeLeft = -33000 - (Math.floor(Date.now() / 7) % 500); // unique, pup-exclusive zone+ // v2.0.44 (mimic-ralph: re-capture failed SILENTLY on every recovered window): the old+ // probe matched the ABSOLUTE -33000 position, but this box CLAMPS off-screen coordinates+ // (the same clamp that forced birth capture to set-diff in 2.0.38) — the moved window+ // never appears at -33000, so the probe never matched, said nothing, and left the window+ // wherever the clamp put it. Identify by bounds CHANGE instead: snapshot every candidate+ // row, move OUR window via CDP, and the one hwnd whose rect changed is ours no matter+ // where Windows actually placed it. Then restore the ORIGINAL rect exactly.+ const _before = new Map((await enumChromeWidgetWindows()).map(r => [r.h, `${r.L},${r.T},${r.W},${r.H}`]));+ const probeLeft = -33000 - (Math.floor(Date.now() / 7) % 500); // still aim off-screen: invisible when NOT clamped await cdp2.send('Browser.setWindowBounds', { windowId, bounds: { left: probeLeft, top: -33000, width: 900, height: 700 } }); let got = null; for (let i = 0; i < 8 && !got; i++) { const rr = await enumChromeWidgetWindows();- const m = rr.find(r => !hwndOwnedByOtherSession(r.h, sessionId) && Math.abs(r.T - Math.round(-33000)) <= 300 && [1, 1.25, 1.5, 1.75, 2].some(sc => Math.abs(r.L - Math.round(probeLeft * sc)) <= 40));- if (m) got = m.h; else await new Promise(r => setTimeout(r, 120));+ const moved = rr.filter(r => !hwndOwnedByOtherSession(r.h, sessionId) && _before.has(r.h) && _before.get(r.h) !== `${r.L},${r.T},${r.W},${r.H}`);+ if (moved.length === 1) got = moved[0].h;+ else if (moved.length > 1) { console.log(`[hwnd] "${sessionId}" re-capture saw ${moved.length} windows move at once — refusing (concurrent motion)`); break; }+ else await new Promise(r => setTimeout(r, 120)); }- // restore on-screen (z-bottom park handles final placement); use original size if we had it- try { await cdp2.send('Browser.setWindowBounds', { windowId, bounds: orig && Number.isFinite(orig.width) ? { left: 0, top: 0, width: orig.width, height: orig.height } : { windowState: 'normal', left: 40, top: 40, width: 1200, height: 800 } }); } catch (e) {}+ if (!got) console.log(`[hwnd] "${sessionId}" re-capture probe: no unique moved window — leaving bare`);+ // restore the ORIGINAL rect exactly (never dump the window at 0,0 — that repositioning+ // was itself user-visible wonk when the clamp made the probe land on-screen)+ try { await cdp2.send('Browser.setWindowBounds', { windowId, bounds: orig && Number.isFinite(orig.width) ? { left: orig.left, top: orig.top, width: orig.width, height: orig.height } : { windowState: 'normal', left: 40, top: 40, width: 1200, height: 800 } }); } catch (e) {} if (got) { try { if (pg) pg.__pupHwnd = got; } catch (e) {} session._hwnd = got; persistSessionHwnd(sessionId, got);