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.43: route pup's taskbar flash + clear through AD-core desktop_flash_window (native, reliable) instead of pup's own FlashWindowEx; browser_alert_window now returns AD's real applied result (no fire-and-forget cleared:true lies)
4 files changed
+35−46
adom-bridge-puppeteer-manifest.json+5−5@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.8.42",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.42/adom-bridge-puppeteer-v1.8.42.zip",- "sha256": "4d8a3965e66381008f7435a07e6a0ae715180456bb0063e7fbc8695eec140a1f",- "size": 160110,+ "version": "1.8.43",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.43/adom-bridge-puppeteer-v1.8.43.zip",+ "sha256": "14435f0ead47ea81a211e5a5f1345df2e108c029d838b96ddc0df42b52ee5d5c",+ "size": 160144, "verbPrefixes": [ "browser_", "desktop_recorder_",@@ -12,5 +12,5 @@ ], "healthEndpoint": "/status", "statusVerb": "browser_readiness",- "released_at": "2026-07-14T22:13:43Z"+ "released_at": "2026-07-14T23:54:24Z" }
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.8.42+1.8.43
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.8.42",+ "version": "1.8.43", "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+28−39@@ -887,6 +887,7 @@ async function rescanProfile(browser, profileName, { adoptOrphans = false } = {} let session = sessions.get(sessionId); if (!session) { session = createSession(profileName);+ session._sessionId = sessionId; // v1.8.43: for AD taskbar/flash titleContains resolution sessions.set(sessionId, session); } else if (session.profileName !== profileName) { // SessionId collision across profiles — shouldn't happen in practice@@ -1084,6 +1085,7 @@ async function recoverSessions() { const session = createSession(profileName); attachTab(session, sessionId, page);+ session._sessionId = sessionId; // v1.8.43: for AD taskbar/flash titleContains resolution sessions.set(sessionId, session); const be = browsers.get(profileName); if (be) be.refCount++;@@ -1645,38 +1647,20 @@ 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, opts) {- if (process.platform !== 'win32') return;+// v1.8.43: pup's taskbar flash routes through AD-CORE desktop_flash_window (native ++// reliable, ONE mechanism for flash AND clear) instead of pup's own FlashWindowEx — which+// duplicated an AD capability, and whose fire-and-forget FLASHW_STOP wouldn't actually+// clear the Win11 attention tint. Resolves the window by its "(session: <id>)" title suffix.+// {stop:true} clears it; otherwise a finite 4-count flash. Returns AD's real result so+// callers can report truthfully (no more cleared:true lies).+async function flashViaAD(sessionId, opts) { 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 psScript = path.join(require('os').tmpdir(), `_adom_flash_${pid}.ps1`);- const ps = [- 'Add-Type @"',- 'using System; using System.Runtime.InteropServices;',- 'public class WinFlash {',- ' [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();',- // 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, $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);- } catch (e) {}+ if (process.platform !== 'win32' || !sessionId) return null;+ const args = opts.stop+ ? { titleContains: `(session: ${sessionId}`, stop: true }+ : { titleContains: `(session: ${sessionId}`, mode: 'count', count: 4 };+ try { return await chrome.adCommand('desktop_flash_window', args, { timeoutMs: 6000 }); }+ catch (e) { return null; } } // v1.8.23: verbs that MUTATE a session's visible state → auto-flash the window so the@@ -1685,16 +1669,16 @@ const MUTATING_VERBS = new Set(['browser_open_window', 'browser_open_tab', 'brow // Debounced auto-flash: one flash per window per ~5s burst of verbs, re-armed after // quiet. {silent:true} on the verb OR session._flashSilent (session default) suppresses.-const _flashState = new Map(); // profileName -> last flash ts+const _flashState = new Map(); // sessionId -> last flash ts function scheduleAutoFlash(session, opts) { opts = opts || {};- if (process.platform !== 'win32' || !session) return;+ if (process.platform !== 'win32' || !session || !session._sessionId) return; if (opts.silent || session._flashSilent) return;- const key = session.profileName;+ const key = session._sessionId; const now = Date.now(); if (now - (_flashState.get(key) || 0) < 5000) return; // coalesce the burst _flashState.set(key, now);- osFlashWindow(key);+ flashViaAD(session._sessionId, {}); // fire-and-forget (async → AD desktop_flash_window) } // Record what the agent just did to a session, for lastAgentUpdate (browser_list_tabs /@@ -2270,7 +2254,7 @@ async function launchSession(sessionId, url, { freshProfile = false, profile, st // real URL's domcontentloaded, which for heavy sites is another 1–3s. Set _flashState so // the open handler's later scheduleAutoFlash debounces (no double flash). Fire-and-forget. if (!foreground && !silent && process.platform === 'win32') {- try { _flashState.set(profileName, Date.now()); osFlashWindow(profileName); } catch (e) {}+ try { _flashState.set(sessionId, Date.now()); flashViaAD(sessionId, {}); } catch (e) {} } if (url) {@@ -2328,6 +2312,7 @@ async function launchSession(sessionId, url, { freshProfile = false, profile, st // resolves in attachTab itself if unset. if (downloadPath) session._downloadPath = downloadPath; attachTab(session, sessionId, page);+ session._sessionId = sessionId; // v1.8.43: for AD taskbar/flash titleContains resolution sessions.set(sessionId, session); activeSessionId = sessionId; @@ -3415,9 +3400,13 @@ const server = http.createServer(async (req, res) => { const session = sessions.get(sessionId); try { 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.' }) });+ // v1.8.43: route through AD's desktop_flash_window (reliable, native). AWAIT the+ // real result — no more fire-and-forget cleared:true lies.+ const r = await flashViaAD(session._sessionId || sessionId, { stop });+ let applied = false;+ try { const o = r && (typeof r.output === 'string' ? JSON.parse(r.output) : r.output || r); applied = !!(o && (o.ok || o.count)); } catch (e) {}+ console.log(`${stop ? 'Cleared' : 'Flashed'} taskbar for session "${sessionId}" (AD applied=${applied})`);+ sendJSON(res, { success: true, output: JSON.stringify({ ok: true, sessionId, alerted: !stop, cleared: stop, applied, _hint: applied ? (stop ? 'Cleared the taskbar flash for this window via AD desktop_flash_window.' : 'Flashed the taskbar (finite 4-count) via AD, no focus steal. Pass {stop:true} to clear.') : 'AD desktop_flash_window did not match the window (title changed? window closed?) — nothing applied.' }) }); } catch (e) { sendJSON(res, { success: false, error: `Alert failed: ${e.message}` }); }