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
Stop the unstick misfire that kept clicking the user's screen (John: 'why does the mouse keep jumping to the upper right corner of the pup window'). The v1.9.183 stuck-occlusion check probed only pup's notion of the active tab; when the user was viewing a DIFFERENT tab of that window, the probed background tab reported hidden (normal), so a healthy window was diagnosed frozen every 30s and the cure ladder fired a real desktop click each time. Now: a window is stuck only if NO tab reports visible (a healthy frontmost window always has one); a visible tab also re-syncs pup's stale activeTabId to what the user is actually viewing; detection must repeat on two consecutive sweeps before any cure runs; and the real-click last resort is hard-capped at once per session per 10 minutes. No evidence, no action.
4 files changed
+50−11
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.9.183",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.183/adom-bridge-puppeteer-v1.9.183.zip",- "sha256": "3ef8cf40e2f62af1d7525c14aeba9ba6d2734916984925a6ce47ad308ec67b97",- "size": 419914,+ "version": "1.9.184",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.184/adom-bridge-puppeteer-v1.9.184.zip",+ "sha256": "2a602cbb59e9b31db91d30276f925b678854396f3206cb1ce56d699e716c03ab",+ "size": 420729, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.183+1.9.184
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.9.183",+ "version": "1.9.184", "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+44−5@@ -2343,14 +2343,45 @@ const _unstickLast = new Map(); // sessionId -> ts of last unstick attempt (do // v1.9.181: hwnd arrives precomputed from the sweep (which already matched it against the OS // foreground), so this probes the ONE matched window — the old version evaluated visibility in EVERY // session every tick, waking throttled renderers bridge-wide (the exact battery/load sin of issue #14).+const _unstickStrikes = new Map(); // sessionId -> consecutive stuck detections+const _unstickClickAt = new Map(); // sessionId -> ts of last REAL-click cure (hard 10-min cap) async function unstickIfHiddenWhileFront(sessionId, session, fgHwnd, hwnd) { if (process.platform !== 'win32' || !session) return false;+ if (!hwnd || String(fgHwnd) !== String(hwnd)) { _unstickStrikes.delete(sessionId); return false; }+ // v1.9.184 (John: "why does the mouse keep jumping to the upper right corner") — the v1.9.183+ // check probed only pup's ACTIVE tab. When the user is viewing a DIFFERENT tab of that window,+ // the probed background tab reports 'hidden' — which is NORMAL — so pup misdiagnosed a healthy+ // window as frozen every sweep and fired its cure ladder, ending in a REAL CLICK on the user's+ // screen every 30s. A window is stuck ONLY if NO tab reports visible: a healthy frontmost window+ // always has exactly one visible tab. Probe across tabs; a visible tab also corrects pup's stale+ // activeTabId to what the user is actually looking at.+ let sawAnswer = false, visibleTab = null;+ for (const t of (session.tabs || [])) {+ if (!t || !t.page || t._crashed) continue;+ try {+ const v = await withTimeout(t.page.evaluate('document.visibilityState'), 1500, 'vis-probe');+ sawAnswer = true;+ if (v === 'visible') { visibleTab = t; break; }+ } catch (e) { /* busy/unreachable tab — not evidence either way */ }+ }+ if (visibleTab) {+ if (session.activeTabId !== visibleTab.tabId) {+ console.log(`[unstick] "${sessionId}" user is viewing ${visibleTab.tabId} (pup thought ${session.activeTabId}) — syncing activeTabId, window is healthy`);+ session.activeTabId = visibleTab.tabId;+ }+ _unstickStrikes.delete(sessionId);+ return false;+ }+ if (!sawAnswer) return false; // no tab answered — cannot judge; never act on no evidence+ // ALL answering tabs hidden while frontmost = the stuck state. Require it TWICE in a row+ // (~30s apart) so a transient (mid-tab-switch, mid-navigation) can never trigger the cure.+ const strikes = (_unstickStrikes.get(sessionId) || 0) + 1;+ _unstickStrikes.set(sessionId, strikes);+ if (strikes < 2) { console.log(`[unstick] "${sessionId}" all tabs hidden while frontmost (strike 1/2) — watching, not acting`); return false; }+ _unstickStrikes.delete(sessionId); const tab = (session.tabs || []).find(t => t.tabId === session.activeTabId) || (session.tabs || [])[0];- if (!tab || !tab.page || tab._crashed) return false;- if (!hwnd || String(fgHwnd) !== String(hwnd)) return false;- let vis = null;- try { vis = await withTimeout(tab.page.evaluate('document.visibilityState'), 1500, 'vis-probe'); } catch (e) { return false; }- if (vis !== 'hidden') return false;+ if (!tab || !tab.page) return false;+ let vis = 'hidden'; const last = _unstickLast.get(sessionId) || 0; if (Date.now() - last < 30000) return false; _unstickLast.set(sessionId, Date.now());@@ -2370,6 +2401,14 @@ async function unstickIfHiddenWhileFront(sessionId, session, fgHwnd, hwnd) { // occlusion only recomputes on real input). Click the EMPTY tab-strip area (right side, clear of // the window buttons): pure activation, cannot hit page content. Acceptable cursor blip: this // branch only runs when the user is ALREADY looking at this frozen window.+ // v1.9.184: HARD CAP — a real click at most once per session per 10 minutes. A misdiagnosis+ // repeating a real click is far worse than a frozen window staying frozen one sweep longer.+ const lastClick = _unstickClickAt.get(sessionId) || 0;+ if (Date.now() - lastClick < 10 * 60000) {+ console.log(`[unstick] "${sessionId}" real-click cure SKIPPED (10-min cap) — soft nudges failed but a repeat click risks disrupting the user`);+ return false;+ }+ _unstickClickAt.set(sessionId, Date.now()); try { const fw = adPayload(await chrome.adCommand('desktop_find_window', { titleContains: `(session: ${sessionId}` }, { timeoutMs: 5000 })); const r = fw && (fw.best || (fw.data && fw.data.best)); const rect = r && r.rect;