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
Dashboard: strip em-dashes from the UI (John caught them, standing rule), fix the flash-all button, and fix taskbar-icon grouping to match the real taskbar. EM-DASHES removed from 7 user-visible strings (title, header, ago-fallback, url/tab fallbacks, overlay-reason line). FLASH: flashViaAD early-returned under minimalTouch and silently swallowed the dashboard buttons too; a human clicking flash is user intent, not automatic machinery, so dashboard actions now pass force:true to bypass the gate while automatic flashing stays suppressed. GROUPING: under minimalTouch pup stamps no per-window AUMID so Windows groups unbranded windows by their Chrome PROCESS (one button per profile); the dashboard was keying by hwnd/session and inventing a row per window, so it showed 3 rows when the taskbar had 2 icons. Now keyed by Chrome process, matching the real taskbar. The earlier dollar-sign identifier collision and backtick-in-comment syntax errors that kept the page from running are confirmed resolved: the served script now parses clean.
4 files changed
+27−17
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.9.232",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.232/adom-bridge-puppeteer-v1.9.232.zip",- "sha256": "5abb78344c2a1a236faf276dffa7317e43246219d15599e6abdda5ee30940395",- "size": 456136,+ "version": "1.9.233",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.233/adom-bridge-puppeteer-v1.9.233.zip",+ "sha256": "a1f471b1f01f38a44a68247597a9f4e78ba052f97c34577d2dced927a312c6a0",+ "size": 456506, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.232+1.9.233
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.9.232",+ "version": "1.9.233", "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+21−11@@ -5801,7 +5801,11 @@ function osClearFlashByActivate(pid, cdpPort) { // {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) {- if (minimalTouch()) return; // v1.9.185 minimal-touch: no cosmetic/shell machinery pushed at Chrome+ // v1.9.233 (John: "the flash all button isn't working"): the minimalTouch gate silently swallowed+ // USER-initiated flashes too. minimal-touch exists to stop pup's AUTOMATIC machinery from touching+ // the taskbar; a human clicking "flash" in the dashboard is the opposite of automatic. Callers that+ // carry explicit user intent pass {force:true} and go through; everything automatic stays gated.+ if (minimalTouch() && !(opts && opts.force)) return; opts = opts || {}; if (process.platform !== 'win32' || !sessionId) return null; const args = opts.stop@@ -7818,7 +7822,13 @@ async function dashboardSnapshot() { const buttons = new Map(); for (const w of wins) { let key, label;- if (minimalTouch()) { key = 'chrome:' + (w.hwnd || w.sessionId); label = 'Chrome (unbranded — minimal-touch)'; }+ // v1.9.233 (John: "why do i see 3 rows for main taskbar icons yet i only see 2 taskbar icons?"):+ // under minimal-touch pup stamps NO per-window AUMID, and Windows groups unbranded windows by+ // their PROCESS - so ALL windows of one Chrome profile share ONE taskbar button. Keying by+ // hwnd/session invented a button per window and the dashboard showed more rows than the real+ // taskbar had icons. Key by PROFILE (= one Chrome process = one button), which is why the header+ // count "chromes N" equals the number of real taskbar icons.+ if (minimalTouch()) { key = 'chrome-process:' + w.profile; label = 'Chrome (' + w.profile + ', unbranded)'; } else if (grouping === 'grouped') { key = 'Adom.Pup'; label = 'Adom Pup (grouped)'; } else { key = (w._curAppId || ('Adom.Pup.' + w.sessionId)); label = w.sessionId; } if (!buttons.has(key)) buttons.set(key, { key, label, grouping: minimalTouch() ? 'minimal-touch' : grouping, windows: [] });@@ -7843,7 +7853,7 @@ async function dashboardSnapshot() { // is served by the bridge itself and must work on a machine with no network. Adom dark theme. const DASHBOARD_HTML = `<!doctype html><html lang="en"><head> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">-<title>pup — live windows</title>+<title>pup live windows</title> <link rel="icon" type="image/svg+xml" href="/dashboard/icon.svg"> <style> *{box-sizing:border-box} html,body{margin:0;background:#0b1116;color:#e6edf3;font:14px/1.5 "Segoe UI",system-ui,sans-serif}@@ -7902,7 +7912,7 @@ footer{color:#5c6c7a;font-size:11px;padding:6px 18px 22px} .why2 b{display:block;color:#12e0c4;margin-bottom:3px;font-weight:600} </style></head><body> <header>- <h1><span class="dot" id="live"></span>pup — live windows</h1>+ <h1><span class="dot" id="live"></span>pup live windows</h1> <span class="pill">windows <b id="cW">0</b></span> <span class="pill">tabs <b id="cT">0</b></span> <span class="pill">chromes <b id="cB">0</b></span>@@ -7923,7 +7933,7 @@ footer{color:#5c6c7a;font-size:11px;padding:6px 18px 22px} // SyntaxError that kills this entire script: the page rendered its static shell and nothing else, // while the SSE stream was fine all along. pup was breaking its own dashboard. Renamed to avoid it. const _q=s=>document.querySelector(s); let SNAP=null;-const ago=ms=>{if(ms==null)return'—';const s=Math.round(ms/1000);if(s<60)return s+'s';const m=Math.round(s/60);if(m<60)return m+'m';const h=Math.floor(m/60);return h+'h '+(m%60)+'m';};+const ago=ms=>{if(ms==null)return'-';const s=Math.round(ms/1000);if(s<60)return s+'s';const m=Math.round(s/60);if(m<60)return m+'m';const h=Math.floor(m/60);return h+'h '+(m%60)+'m';}; const esc=t=>String(t==null?'':t).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); function render(){ if(!SNAP)return; const d=SNAP;@@ -7973,10 +7983,10 @@ function render(){ +'</div>' +(w.foreground&&w.foregroundReason?'<div class="why"><b>why foreground:</b> '+esc(w.foregroundReason)+'</div>':'') +'<table>'- +'<tr><td class="k">url</td><td class="url">'+esc(w.url||'—')+'</td></tr>'+ +'<tr><td class="k">url</td><td class="url">'+esc(w.url||'-')+'</td></tr>' +'<tr><td class="k">overlay</td><td>'+esc(w.overlay||'none') +(w.overlayWhy?'<div class="why2"><b>how this icon was chosen</b>'- +'<div>tab: '+esc(w.overlayWhy.tabId||'?')+' — '+esc(w.overlayWhy.tabPickedBy)+'</div>'+ +'<div>tab: '+esc(w.overlayWhy.tabId||'?')+', '+esc(w.overlayWhy.tabPickedBy)+'</div>' +'<div>source: '+esc(w.overlayWhy.source||'none')+'</div>' +'<div>fetched via: '+esc(w.overlayWhy.fetchedVia)+'</div>' +'<div>composed: '+esc(w.overlayWhy.composed)+'</div>'@@ -7987,7 +7997,7 @@ function render(){ +'</table>' +'<div class="tabs">'+w.tabs.map(t=>'<div class="tab '+(t.active?'act':'')+'">' +'<img class="fav" src="/dashboard/favicon?session='+encodeURIComponent(w.sessionId)+'&tab='+encodeURIComponent(t.tabId)+'" data-fallback="hide">'- +'<span class="id">'+esc(t.tabId)+'</span><span class="u">'+esc(t.title||t.url||'—')+'</span>'+ +'<span class="id">'+esc(t.tabId)+'</span><span class="u">'+esc(t.title||t.url||'-')+'</span>' +(t.crashed?'<span class="x">crashed</span>':'') +'<button class="btn xs" data-act="close-tab" data-sid="'+esc(w.sessionId)+'" data-tid="'+esc(t.tabId)+'">×</button>' +'</div>').join('')+'</div>'@@ -8108,11 +8118,11 @@ const server = http.createServer(async (req, res) => { try { await tab.page.close(); } catch (e) {} return done({ ok: true, closed: tid }); }- if (act === 'flash' && sid) { try { await flashViaAD(sid, {}); } catch (e) {} return done({ ok: true }); }- if (act === 'unflash' && sid) { try { await flashViaAD(sid, { stop: true }); } catch (e) {} return done({ ok: true }); }+ if (act === 'flash' && sid) { try { await flashViaAD(sid, { force: true }); } catch (e) {} return done({ ok: true }); }+ if (act === 'unflash' && sid) { try { await flashViaAD(sid, { stop: true, force: true }); } catch (e) {} return done({ ok: true }); } if (act === 'flash-all' || act === 'unflash-all') { const stop = act === 'unflash-all';- for (const id of [...sessions.keys()]) { try { await flashViaAD(id, stop ? { stop: true } : {}); } catch (e) {} }+ for (const id of [...sessions.keys()]) { try { await flashViaAD(id, stop ? { stop: true, force: true } : { force: true }); } catch (e) {} } return done({ ok: true, n: sessions.size }); } return done({ ok: false, error: 'unknown action' });