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
The adom-theme foreground disruption, ROOT-CAUSED and fixed at the source (John: 'i don't believe you that chrome is auto-foregrounding the windows' - he was right to push back; the evidence for self-raising was my own scheduled re-assert log lines, not detected raises). The REAL mechanism, from reading the code paths: (1) Target.createTarget{newWindow} can grab OS focus at creation (the codebase's own launch-path comments acknowledge the same grab at browser launch); (2) the immediate 'backgrounded' call resolves its target as the LARGEST VISIBLE window of the process (BestWindow) - wrong window whenever the shared profile has several, so it claimed success while the new window kept its creation focus; (3) the post-nav park moved the still-FOCUSED window on-screen, surfacing it in the user's face; (4) osBottomUnlessForeground then read that focus as user intent and refused to bottom it until the user clicked away. Fixes: creation now MEASURES the OS foreground before/after createTarget and logs it (no more theories), and if focus moved it is handed straight back to the user's window (AttachThreadInput restore) while the new window is still off-screen and invisible - so parks act on an unfocused window and nothing ever surfaces; osBackgroundWindowByPid accepts the session's own hwnd and only falls back to BestWindow when none is known; browser_lower passes it. The 1.9.252 fg-guard stays as a safety net but the narrative is corrected: the code's own doctrine (v1.8.49) is that Chrome does NOT self-raise after startup.
4 files changed
+70−14
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.9.252",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.252/adom-bridge-puppeteer-v1.9.252.zip",- "sha256": "14dac85ae41fc539350c2d058b04e426c7a776b716530b06132ac0d940781937",- "size": 471245,+ "version": "1.9.253",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.253/adom-bridge-puppeteer-v1.9.253.zip",+ "sha256": "55d98b473a5acba1b863d63530ca75d869ddb9442f73c7f2f4029ca8cfd6ae01",+ "size": 472458, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.252+1.9.253
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.9.252",+ "version": "1.9.253", "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. browser_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. browser_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, browser_alert_window posts a native toast whose click focuses the window, browser_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-desktop", "author": "Adom Inc.",
src/server.js+64−8@@ -5082,7 +5082,7 @@ async function badgePupTaskbar(sessionId) { // foreground window after, so callers (and tests) know for CERTAIN whether it worked. // cdpPort (the browser's CDP debug port) resolves the real browser via its LISTENING // process — reliable across the launcher→browser pid relaunch that Edge/Chrome do.-function osBackgroundWindowByPid(pid, cdpPort, restoreHwnd, respectUser) {+function osBackgroundWindowByPid(pid, cdpPort, restoreHwnd, respectUser, targetHwnd) { if (process.platform !== 'win32' || (!pid && !cdpPort)) return 'error'; try { const port = Number(cdpPort) || 0;@@ -5147,7 +5147,12 @@ function osBackgroundWindowByPid(pid, cdpPort, restoreHwnd, respectUser) { '$anchors = @($pidList.ToArray())', 'foreach ($tp in $anchors) { Get-CimInstance Win32_Process -Filter "ParentProcessId=$tp" -ErrorAction SilentlyContinue | Where-Object { $_.Name -in @("chrome.exe","msedge.exe") } | ForEach-Object { [void]$pidList.Add([int]$_.ProcessId) } }', // Resolve the ONE real browser window (largest visible Chrome_WidgetWin_1), not a helper.- '$pup = [WinBgP]::BestWindow([int[]]@($pidList.ToArray()))',+ // v1.9.253 (the adom-theme disruption): BestWindow = "largest visible window of the process"+ // picked the WRONG window whenever the shared profile had several - the background call then+ // claimed success while the actual new window kept its creation focus. When the caller knows+ // the session's own hwnd, use it; BestWindow only as the no-hwnd fallback.+ `$explicit = [IntPtr]${Number(targetHwnd) || 0}`,+ '$pup = if ($explicit -ne [IntPtr]::Zero) { $explicit } else { [WinBgP]::BestWindow([int[]]@($pidList.ToArray())) }', 'if ($pup -eq [IntPtr]::Zero) { Write-Output "no-window"; exit }', // respectUser (v1.8.49): used ONLY by LATE re-parks (post-resize etc.), never the launch // passes. Rationale: after startup, Chrome does NOT self-raise — so if the pup window is@@ -6676,6 +6681,40 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer // always makes a tab; only CDP Target.createTarget accepts newWindow. Page identity is resolved by // diffing browser.pages() before/after rather than matching internal target ids, so this stays // correct across puppeteer versions. Returns null if the window never materialises.+function osRestoreForeground(hwnd) {+ // Give the USER's window its focus back (SetForegroundWindow needs the AttachThreadInput trick+ // when called from a background process). Only ever called with the user's prior-foreground hwnd.+ if (process.platform !== 'win32' || !hwnd) return false;+ try {+ const scriptPath = path.join(require('os').tmpdir(), `_adom_restorefg_${process.pid}.ps1`);+ const ps = [+ 'Add-Type @"',+ 'using System; using System.Runtime.InteropServices;',+ 'public class WinRf {',+ ' [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();',+ ' [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);',+ ' [DllImport("user32.dll")] public static extern bool AttachThreadInput(uint a, uint b, bool f);',+ ' [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);',+ ' [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr h);',+ ' [DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();',+ '}',+ '"@',+ `$t = [IntPtr]${Number(hwnd)}`,+ '$fg = [WinRf]::GetForegroundWindow()',+ 'if ($fg -eq $t) { Write-Output "already"; exit }',+ '$p1 = 0; $tid1 = [WinRf]::GetWindowThreadProcessId($fg, [ref]$p1)',+ '$me = [WinRf]::GetCurrentThreadId()',+ '[void][WinRf]::AttachThreadInput($me, $tid1, $true)',+ '[void][WinRf]::SetForegroundWindow($t)',+ '[void][WinRf]::BringWindowToTop($t)',+ '[void][WinRf]::AttachThreadInput($me, $tid1, $false)',+ 'Write-Output "restored"',+ ].join(String.fromCharCode(13, 10));+ fs.writeFileSync(scriptPath, ps);+ const out = runPsHidden(scriptPath, { timeout: 5000 }).trim();+ return out === 'restored' || out === 'already';+ } catch (e) { return false; }+} async function createPageInNewWindow(browser) { let cdp = null; try {@@ -6687,13 +6726,29 @@ async function createPageInNewWindow(browser) { // these are explicit PER-WINDOW coords on this one creation, and the unconditional park // (1.9.138) immediately rewrites the normal rect on-screen at bottom-z, so nothing can stay // stranded. Windows whose park somehow fails are caught by healOffscreenWindow.+ // v1.9.253 (John: "i don't believe you that chrome is auto-foregrounding the windows" — he was+ // right to push: MEASURE it). Capture the user's foreground before creating the window; check it+ // after. If creation stole focus, hand it straight back while the new window is still OFF-SCREEN+ // (invisible), so the user's typing is interrupted for at most ~1s and NOTHING ever appears on+ // their screen. This is the at-the-source fix; parks and re-asserts then act on an unfocused+ // window (osBottomUnlessForeground no longer mistakes the creation-grab for user intent).+ const prevFg = osGetForegroundWindow(); await cdp.send('Target.createTarget', { url: 'about:blank', newWindow: true, left: -32000, top: -32000, width: 1280, height: 800 });- for (let i = 0; i < 50; i++) {- const fresh = (await browser.pages()).find(p => !before.has(p));- if (fresh) return fresh;- await new Promise(r => setTimeout(r, 100));+ let fresh = null;+ for (let i = 0; i < 50 && !fresh; i++) {+ fresh = (await browser.pages()).find(p => !before.has(p));+ if (!fresh) await new Promise(r => setTimeout(r, 100)); }- return null;+ try {+ const nowFg = osGetForegroundWindow();+ if (prevFg && nowFg && String(nowFg) !== String(prevFg)) {+ const restored = osRestoreForeground(prevFg);+ console.log(`[focus] MEASURED: createTarget(newWindow) STOLE the OS foreground (fg ${prevFg} -> ${nowFg}); restored to the user: ${restored}`);+ } else {+ console.log(`[focus] MEASURED: createTarget(newWindow) did NOT move the OS foreground (fg=${prevFg || '?'})`);+ }+ } catch (e) {}+ return fresh; } catch (e) { console.log(`[window] createPageInNewWindow failed: ${e.message}`); return null;@@ -10149,7 +10204,8 @@ const server = http.createServer(async (req, res) => { // keeps rendering hidden windows). Per-process, so shared-profile siblings hide too. ok = await macSetAppHiddenByPid(macSessionPid(session), true); } else {- ok = (be && be.browser) ? (osBackgroundWindowByPid(be.browser._detachedPid, be.browser._cdpPort, be.browser._priorForeground) === 'backgrounded') : osSendWindowToBack(session.profileName);+ if (!session._hwnd) { try { await resolveSessionHwndByBounds(session, sessionId); } catch (e) {} }+ ok = (be && be.browser) ? (osBackgroundWindowByPid(be.browser._detachedPid, be.browser._cdpPort, be.browser._priorForeground, false, session._hwnd) === 'backgrounded') : osSendWindowToBack(session.profileName); } session._foreground = false; sendJSON(res, { success: true, output: JSON.stringify({ ok: true, sessionId, placement: 'background', applied: ok, _hint: 'Window sent behind the user\'s windows (still drivable/screenshottable). Keep pup in the background unless the user asked to watch.' }) });