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
v1.8.35: browser_alert_window {stop:true} clears the taskbar highlight (FLASHW_STOP); fix alert_window to actually flash (was wrongly calling bringToFront)
4 files changed
+27−22
adom-bridge-puppeteer-manifest.json+5−5@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.8.34",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.34/adom-bridge-puppeteer-v1.8.34.zip",- "sha256": "70d25fb8e42555c176b906d9891b34d44b8e5c035cbd8ee0b8f3a7769e9efe4f",- "size": 157011,+ "version": "1.8.35",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.35/adom-bridge-puppeteer-v1.8.35.zip",+ "sha256": "ac811b0b884e2a24f65e28c72da62a20107fbc804d610f85c280faf1a8c847b1",+ "size": 157339, "verbPrefixes": [ "browser_", "desktop_recorder_",@@ -12,5 +12,5 @@ ], "healthEndpoint": "/status", "statusVerb": "browser_readiness",- "released_at": "2026-07-14T20:05:21Z"+ "released_at": "2026-07-14T20:37:36Z" }
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.8.34+1.8.35
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.8.34",+ "version": "1.8.35", "description": "Drives a browser ALREADY on the machine — installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) — 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 → Edge → 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, …). 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 — 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 — a separate bridge, not pup's job.", "homepage": "https://wiki.adom.inc/adom/adom-desktop", "author": "Adom Inc.",
src/server.js+20−15@@ -1615,13 +1615,14 @@ function osBackgroundWindowByPid(pid, cdpPort, restoreHwnd) { // v1.8.24: a FINITE 4-blink pulse then stop (see Flash() below) — NOT a blink-forever // nag. Skips if the window is already foreground. Fire-and-forget (async exec) so it // never adds latency to the driving verb.-function osFlashWindow(profileName) {+function osFlashWindow(profileName, opts) { if (process.platform !== 'win32') return;+ opts = opts || {};+ const stop = !!opts.stop; // v1.8.35: {stop:true} clears the taskbar highlight (FLASHW_STOP) const be = browsers.get(profileName); const pid = (be && be.browser && (be.browser._detachedPid || (be.browser.process() && be.browser.process().pid))) || null; if (!pid) return; try {- const { exec } = require('child_process'); const psScript = path.join(require('os').tmpdir(), `_adom_flash_${pid}.ps1`); const ps = [ 'Add-Type @"',@@ -1630,17 +1631,18 @@ function osFlashWindow(profileName) { ' [StructLayout(LayoutKind.Sequential)] public struct FLASHWINFO { public uint cbSize; public IntPtr hwnd; public uint dwFlags; public uint uCount; public uint dwTimeout; }', ' [DllImport("user32.dll")] public static extern bool FlashWindowEx(ref FLASHWINFO pwfi);', ' [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();',- ' public static void Flash(IntPtr h){ if(h==IntPtr.Zero) return; if(GetForegroundWindow()==h) return;',- // v1.8.24: FINITE pulse — FLASHW_TRAY (taskbar button only), 4 blinks, then STOP.- // NOT FLASHW_TIMERNOFG (which blinks forever until focused — an annoying nag). A short- // orange pulse is a glance-and-forget "this window was updated" nudge, not a demand.- ' FLASHWINFO fi=new FLASHWINFO(); fi.cbSize=(uint)Marshal.SizeOf(fi); fi.hwnd=h; fi.dwFlags=0x2; fi.uCount=4; fi.dwTimeout=0; FlashWindowEx(ref fi); }',+ // v1.8.24: FINITE pulse — FLASHW_TRAY (taskbar button), 4 blinks, then stop (NOT+ // FLASHW_TIMERNOFG, which blinks forever = a nag). v1.8.35: stop=true → FLASHW_STOP+ // (dwFlags 0, count 0) to CLEAR a lingering highlight on demand.+ ' public static void Flash(IntPtr h, bool stop){ if(h==IntPtr.Zero) return; if(!stop && GetForegroundWindow()==h) return;',+ ' FLASHWINFO fi=new FLASHWINFO(); fi.cbSize=(uint)Marshal.SizeOf(fi); fi.hwnd=h; fi.dwFlags=(stop?(uint)0:(uint)0x2); fi.uCount=(stop?(uint)0:(uint)4); fi.dwTimeout=0; FlashWindowEx(ref fi); }', '}', '"@', `$targetPid = ${pid}`,+ `$doStop = ${stop ? '$true' : '$false'}`, '$p = Get-Process -Id $targetPid -ErrorAction SilentlyContinue',- 'if ($p -and $p.MainWindowHandle -ne 0) { [WinFlash]::Flash($p.MainWindowHandle) }',- 'else { Get-CimInstance Win32_Process -Filter "ParentProcessId=$targetPid" -ErrorAction SilentlyContinue | Where-Object { $_.Name -in @("chrome.exe","msedge.exe") } | ForEach-Object { $cp = Get-Process -Id $_.ProcessId -ErrorAction SilentlyContinue; if ($cp -and $cp.MainWindowHandle -ne 0) { [WinFlash]::Flash($cp.MainWindowHandle) } } }',+ 'if ($p -and $p.MainWindowHandle -ne 0) { [WinFlash]::Flash($p.MainWindowHandle, $doStop) }',+ 'else { Get-CimInstance Win32_Process -Filter "ParentProcessId=$targetPid" -ErrorAction SilentlyContinue | Where-Object { $_.Name -in @("chrome.exe","msedge.exe") } | ForEach-Object { $cp = Get-Process -Id $_.ProcessId -ErrorAction SilentlyContinue; if ($cp -and $cp.MainWindowHandle -ne 0) { [WinFlash]::Flash($cp.MainWindowHandle, $doStop) } } }', ].join('\r\n'); fs.writeFileSync(psScript, ps); runPsHiddenAsync(psScript);@@ -2894,7 +2896,7 @@ const server = http.createServer(async (req, res) => { V('browser_use', 'Pin (or clear) the browser pup drives by default: chrome | edge | cft | auto (or an explicit executablePath). "cft" fetches Chrome for Testing then pins it. With {browser:"chrome", install:true} it DOWNLOADS & INSTALLS real Google Chrome on an Edge-only box, then pins it — SILENT on a normal PC; on a locked-down box (VM/managed device) it needs a Windows approval, so the bridge pops a native "click YES on the UAC" notification and, if it expires, re-notifies with a one-tap "Approve now" (all autonomous — no AI turn). Poll browser_readiness.chromeStablePhase: installing → awaiting_uac → ready. Persists across restarts; launch still spawn-verifies + falls back.', { browser: '"chrome" | "edge" | "cft" | "auto"', install: 'optional bool — with browser:"chrome", install real Chrome if missing', elevate: 'optional "auto"(default)|true|false — auto: quiet install, prompt only if it needs admin; false: never prompt; true: go straight to the prompt', executablePath: 'optional — pin an exact browser binary' }, 240, {}), V('browser_lower_os_window', 'Send a pup window to the BACKGROUND (behind the user\'s windows) without stealing focus — still fully drivable. The default for open; use this to re-hide a window you foregrounded.', { sessionId: 'optional str' }, 15, {}), V('browser_raise_os_window', 'Bring a pup window to the FOREGROUND (user\'s screen). Use SPARINGLY — only when the user EXPLICITLY must SEE it (a demo, recording, "show me"). It STEALS focus and can leak a mid-typed keystroke into the page — to just signal "I updated this window", use browser_alert_window (or rely on the auto-flash), NOT raise. pup blurs the page\'s focused element before raising to reduce the keystroke leak. Alias: browser_focus_window.', { sessionId: 'optional str' }, 15, {}),- V('browser_alert_window', 'Flash the taskbar icon for a pup window — a non-intrusive "it\'s ready/DONE" nudge that does NOT steal focus. NOTE (v1.8.23): mutating verbs already AUTO-FLASH (debounced, one per ~5s burst per window) so the user always knows the agent touched a window — use this for an explicit end-of-task "it\'s ready" nudge on top of that. STRONGLY prefer this (or the auto-flash) over browser_raise_os_window, which steals focus and can leak a mid-typed keystroke into the page.', { sessionId: 'optional str' }, 15, {}),+ V('browser_alert_window', 'Flash the taskbar icon for a pup window — a non-intrusive "it\'s ready/DONE" nudge that does NOT steal focus. NOTE (v1.8.23): mutating verbs already AUTO-FLASH (debounced, one per ~5s burst per window) so the user always knows the agent touched a window — use this for an explicit end-of-task "it\'s ready" nudge on top of that. STRONGLY prefer this (or the auto-flash) over browser_raise_os_window, which steals focus and can leak a mid-typed keystroke into the page.', { sessionId: 'optional str', stop: 'optional bool — CLEAR the highlight instead of flashing' }, 15, {}), V('browser_render_html', 'Render a local HTML string in the tab via page.setContent — UTF-8-correct (a data: URL mojibakes accented chars without an explicit charset). Great for previewing generated markup without a temp file. Auto-flashes the window.', { sessionId: 'required str', html: 'required HTML string', waitUntil: 'optional (domcontentloaded default)', updateNote: 'optional note for lastAgentUpdate', silent: 'optional bool — suppress the auto-flash' }, 30, { sessionId: 'work', html: '<h1>Café</h1>' }), // Session/tab/window management + probes (were routed but undocumented). V('browser_status', 'List all open pup sessions + their tabs (NOT a readiness check — use browser_readiness for that).', {}, 10, {}),@@ -3335,7 +3337,10 @@ const server = http.createServer(async (req, res) => { } case 'browser_alert_window': {- // Flashes the taskbar icon for a session's Chrome window (does NOT bring to foreground)+ // Flash the taskbar button for a session's window WITHOUT foregrounding it. v1.8.35:+ // {stop:true} (alias {clear:true}) CLEARS a lingering orange highlight instead.+ // (Pre-1.8.35 this wrongly called bringToFront — it raised the window instead of+ // flashing. Now it uses the real FlashWindowEx path, same as the auto-flash.) const sessionId = args.sessionId || activeSessionId; if (!sessionId || !sessions.has(sessionId)) { sendJSON(res, { success: false, error: `Session "${sessionId}" not found` });@@ -3343,10 +3348,10 @@ const server = http.createServer(async (req, res) => { } const session = sessions.get(sessionId); try {- await session.page.bringToFront();- activeSessionId = sessionId;- console.log(`Alerted session "${sessionId}" (taskbar flash)`);- sendJSON(res, { success: true, output: JSON.stringify({ ok: true, sessionId, alerted: true }) });+ const stop = !!(args.stop || args.clear);+ osFlashWindow(session.profileName, { stop });+ console.log(`${stop ? 'Cleared' : 'Flashed'} taskbar for session "${sessionId}"`);+ sendJSON(res, { success: true, output: JSON.stringify({ ok: true, sessionId, alerted: !stop, cleared: stop, _hint: stop ? 'Cleared the taskbar highlight for this window (FLASHW_STOP).' : 'Flashed the taskbar (finite ~4-pulse) without stealing focus. Pass {stop:true} to clear it.' }) }); } catch (e) { sendJSON(res, { success: false, error: `Alert failed: ${e.message}` }); }