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
Settings dialog round 2, all from John's live review. (1) PICTURES: users won't know what AUMID is, so the dialog now shows real screenshots - the AUMID row shows his actual taskbar in both states (off: windows share one Chrome button / on: every window its own icon, from the split-mode era captures); the icon-style row shows the real standard tile next to the five category tiles (wiki signed-out, wiki signed-IN padlock, open web, adom app, mixed) extracted from the shipped .ico files; the jump-list row shows an illustration built from pup's REAL jump-list entries (Switch to logged-in view / Switch to public view). Served from icons/settings-ex via /dashboard/example. (2) TEST BUTTONS: a 'test' button on the caption row shows a sample AI-foreground caption at the configured duration (new test-caption action), and the orange-highlight row got 'test highlight' / 'clear highlight' buttons with a live count of open pup windows right beside them so the user knows exactly what will light up. (3) CONSISTENT NAME: the FlashWindowEx attention state is now called the ORANGE HIGHLIGHT in every user-visible string (buttons, tooltips, toasts, settings labels) - John always calls it that and the UI calling it 'flash' made settings unrecognizable; internal identifiers keep their names (UI-language rule, recorded in the dev skill). (4) DEFAULT CORRECTED: orange highlight on new window defaults ON (it always had been until my settings build wrongly defaulted it off); stored 'off' values migrate to on unless the user chose off in the dialog themselves (choice-marker).
4 files changed
+48−22
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.9.261",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.261/adom-bridge-puppeteer-v1.9.261.zip",- "sha256": "711bf02f0c1d14994b383eadbda1bd8f02178bea59238e5ea13a42a84782c757",- "size": 476683,+ "version": "1.9.262",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.262/adom-bridge-puppeteer-v1.9.262.zip",+ "sha256": "5b1f680cf4249d4a7d1d8851f36ff2f284ab2eb87db8f69bbf9dd2283910a36e",+ "size": 559761, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.261+1.9.262
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.9.261",+ "version": "1.9.262", "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+42−16@@ -2712,6 +2712,8 @@ function pupSettings() { const D = { aumidIcons: 'off', jumpLists: 'off', fgWatch: 'on', fgCaption: 'on', fgCaptionMs: 2000, overlayBadges: 'on', autoFlash: 'off', sleepRecovery: 'on', openFlash: 'off', aumidIconStyle: 'standard' };+ D.openFlash = 'on'; // v1.9.262 (John: "why did you default it to off when its defaulted to on")+ if (_pupSettings.openFlash === 'off' && !_pupSettings._openFlashChosen) _pupSettings.openFlash = 'on'; // v1.9.260 (John: "2s feels too fast cuz i can't read"): readable default. A stored 2000 was the // old default, not a choice made in the dialog (it shipped hours ago) - migrate it up once. if (D.fgCaptionMs) D.fgCaptionMs = 3500;@@ -8262,6 +8264,7 @@ select,input{background:#111b23;color:#e6edf3;border:1px solid #23303c;border-ra #dlg{position:absolute;top:60px;left:50%;transform:translateX(-50%);width:min(560px,92vw);max-height:calc(100vh - 120px);overflow:auto;background:#161b22;border:1px solid #30363d;border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.6)} .dlghead{position:sticky;top:0;display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:#161b22;border-bottom:1px solid #30363d;font-family:"Familjen Grotesk",Satoshi,sans-serif;font-weight:600;color:#e6edf3} #dlgBody{padding:6px 16px 16px}+.sexample{display:block;max-width:100%;margin-top:8px;border:1px solid #30363d;border-radius:8px} .srow{display:flex;align-items:flex-start;gap:12px;padding:11px 0;border-bottom:1px solid #21262d} .srow:last-child{border-bottom:none} .srow .stxt{flex:1 1 auto;min-width:0}@@ -8352,8 +8355,8 @@ footer{color:#5c6c7a;font-size:11px;padding:8px 18px 22px} </div> <div class="acts2"> <button class="ibtn" id="gear" title="pup settings" aria-label="settings"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h0a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h0a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v0a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>- <button class="ibtn" data-act="flash-all" title="flash every pup taskbar button" aria-label="flash all"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></button>- <button class="ibtn" data-act="unflash-all" title="clear every flash" aria-label="clear flashes"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/><line x1="3" y1="3" x2="21" y2="21"/></svg></button>+ <button class="ibtn" data-act="flash-all" title="orange-highlight every pup taskbar button" aria-label="orange-highlight all"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></button>+ <button class="ibtn" data-act="unflash-all" title="clear every orange highlight" aria-label="clear orange highlights"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/><line x1="3" y1="3" x2="21" y2="21"/></svg></button> <button class="ibtn danger" data-act="close-all" data-confirm="Close ALL pup windows?" title="close every pup window" aria-label="close all windows"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button> </div> </header>@@ -8447,7 +8450,7 @@ function render(){ +'<span class="led '+esc(g.led||'red')+'" title="overlay match: '+esc(g.ledWhy||'')+'"></span>' +'<span class="nm">'+esc(g.label)+'</span>' +'<span class="meta">'+g.wins.length+' window'+(g.wins.length>1?'s':'')+' · '+esc(g.grouping)+' · badge from '+esc(g.badgeVia||'?')+'</span>'- +'<span class="acts"><button class="btn sm" data-act="flash-group" data-ids="'+esc(g.windows.join(','))+'">flash</button>'+ +'<span class="acts"><button class="btn sm" data-act="flash-group" data-ids="'+esc(g.windows.join(','))+'">highlight</button>' +'<button class="btn sm" data-act="unflash-group" data-ids="'+esc(g.windows.join(','))+'">clear</button></span></div>' +'<div class="tw'+(gcoll?' hidden':'')+'">'+g.wins.map(w=>{ const wid=gid+'-'+esc(w.sessionId), wcoll=COLL.has(wid);@@ -8467,7 +8470,7 @@ function render(){ +'<span class="acts">' +'<button class="btn sm" data-act="fg" data-sid="'+esc(w.sessionId)+'">bring to fg</button>' +'<button class="btn sm" data-act="bg" data-sid="'+esc(w.sessionId)+'">send to bg</button>'- +'<button class="btn sm" data-act="flash" data-sid="'+esc(w.sessionId)+'">flash</button>'+ +'<button class="btn sm" data-act="flash" data-sid="'+esc(w.sessionId)+'">highlight</button>' +'<button class="btn sm" data-act="unflash" data-sid="'+esc(w.sessionId)+'">clear</button>' +'<button class="btn sm danger" data-act="close-window" data-sid="'+esc(w.sessionId)+'" data-confirm="Close '+esc(w.sessionId)+'?">close</button></span></div>' +'<div class="'+(wcoll?'hidden':'')+'">'@@ -8493,7 +8496,7 @@ function toast(msg,type){ _q('#toasts').appendChild(t); setTimeout(()=>{t.classList.add('out');setTimeout(()=>t.remove(),350)},type==='error'?8000:4000); }-const ACTMSG={'flash':'flashed','unflash':'flash cleared on','flash-all':'flashed every pup taskbar button','unflash-all':'cleared all flashes','close-all':'closed all pup windows','close-window':'closed','close-tab':'closed tab in','fg':'bringing to front:','bg':'sent to background:','set-setting':'setting saved'};+const ACTMSG={'flash':'orange-highlighted','unflash':'orange highlight cleared on','flash-all':'orange-highlighted every pup taskbar button','unflash-all':'cleared every orange highlight','close-all':'closed all pup windows','close-window':'closed','close-tab':'closed tab in','fg':'bringing to front:','bg':'sent to background:','set-setting':'setting saved','test-caption':'sample caption shown on your screen'}; async function act(p){ try{ const r=await fetch('/dashboard/action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(p)});@@ -8508,16 +8511,16 @@ let SEENTOAST=0; // v1.9.259: the settings dialog. Rows defined once; state comes from SNAP.bridge.settings; every // change POSTs set-setting and answers back with a toast. const SETTINGS_DEF=[- {k:'aumidIcons',l:'AUMID taskbar icons',d:'EXPERIMENT: give every pup window its OWN taskbar icon by stamping a per-window identity. Toggle on/off to test whether AUMID stamping is what crashes Chrome. Off = all windows share one Chrome button (current mode). Already-stamped windows keep their icon until closed.'},+ {k:'aumidIcons',l:'AUMID taskbar icons',d:'EXPERIMENT: give every pup window its OWN taskbar icon by stamping a per-window identity. Toggle on/off to test whether AUMID stamping is what crashes Chrome. Off = all windows share one Chrome button (current mode). Already-stamped windows keep their icon until closed.',img:'aumid'}, {k:'taskbarGrouping',l:'AUMID grouping',d:'Only applies when AUMID icons are on: split = one icon per window; grouped = all pup windows under one Adom Pup icon.',sel:['split','grouped'],dep:'aumidIcons'},- {k:'aumidIconStyle',l:'AUMID icon style',d:'Only applies when AUMID icons are on: standard = one pup tile for every window; category = the custom set (wiki signed-out, wiki signed-in padlock, world icon for the open web, adom app icon, mixed).',sel:['standard','category'],dep:'aumidIcons'},- {k:'jumpLists',l:'Jump lists',d:'Build right-click jump-list menus on pup taskbar icons. Known to blip on Windows 11; off by default.'},+ {k:'aumidIconStyle',l:'AUMID icon style',d:'Only applies when AUMID icons are on: standard = one pup tile for every window; category = the custom set (wiki signed-out, wiki signed-in padlock, world icon for the open web, adom app icon, mixed).',sel:['standard','category'],dep:'aumidIcons',img:'iconstyle'},+ {k:'jumpLists',l:'Jump lists',d:'Build right-click jump-list menus on pup taskbar icons (illustrated below). Known to blip on Windows 11; off by default.',img:'jumplist'}, {k:'fgWatch',l:'20s foreground watch',d:'For 20s after a window opens or navigates in the background, watch whether something (JavaScript, Chrome, a focus change) brings it to the foreground, and caption that it was not Adom. Never moves the window.'}, {k:'fgCaption',l:'Caption on AI foreground',d:'When an AI brings a window to your screen, show its stated reason as an on-screen caption. The AI is always forced to give a reason; this shows it to you.'},- {k:'fgCaptionMs',l:'',d:'',slider:[500,5000,250],dep:'fgCaption'},+ {k:'fgCaptionMs',l:'',d:'',slider:[500,5000,250],dep:'fgCaption',test:1}, {k:'overlayBadges',l:'Favicon overlay badges',d:'Paint each pup taskbar icon with the favicon of its active tab plus the Adom mark.'},- {k:'openFlash',l:'Orange highlight on new window',d:'Flash the taskbar button orange when a new pup window opens, so you can spot it.'},- {k:'autoFlash',l:'Flash on agent activity',d:'Flash a window taskbar button when an AI changes that window (navigate, click, type). One flash per burst.'},+ {k:'openFlash',l:'Orange highlight on new window',d:'Orange-highlight the taskbar button when a new pup window opens, so you can spot it.',testflash:1},+ {k:'autoFlash',l:'Orange highlight on agent activity',d:'Orange-highlight a window taskbar button when an AI changes that window (navigate, click, type). One highlight per burst.'}, {k:'agentActivity',l:'Taskbar progress bar',d:'Show a progress strip on a window taskbar button while an AI is actively driving it.'}, {k:'sleepRecovery',l:'Sleep and crash recovery',d:'After the laptop wakes or Chrome dies, automatically bring every pup window back at its last page, signed in. Never restarts a live Chrome.'}, {k:'autoLogin',l:'Auto-fill saved logins',d:'Fill (never submit) a matching saved login when a login page loads in a pup window.'},@@ -8527,20 +8530,25 @@ const SETTINGS_DEF=[ function renderDlg(){ const st=(SNAP&&SNAP.bridge&&SNAP.bridge.settings)||{}; _q('#dlgBody').innerHTML=SETTINGS_DEF.map(def=>{+ const exImg=def.img?'<img class="sexample" src="/dashboard/example?n='+def.img+'" alt="example" data-fallback="hide">':''; if(def.slider){ const on=st[def.dep]==='on';- const v=Number(st[def.k])||2000;+ const v=Number(st[def.k])||3500; return '<div class="sub'+(on?'':' sdis')+'" id="row-'+def.k+'"><span class="sdesc">caption duration</span>' +'<input type="range" min="'+def.slider[0]+'" max="'+def.slider[1]+'" step="'+def.slider[2]+'" value="'+v+'" data-slider="'+def.k+'" aria-label="caption duration">'- +'<span class="sval" id="val-'+def.k+'">'+(v/1000).toFixed(1)+'s</span></div>';+ +'<span class="sval" id="val-'+def.k+'">'+(v/1000).toFixed(1)+'s</span>'+ +(def.test?'<button class="btn sm" data-testcap="1" title="show a sample caption on your screen right now, at the configured duration">test</button>':'')+'</div>'; } if(def.sel){ const on=!def.dep||st[def.dep]==='on';- return '<div class="sub'+(on?'':' sdis')+'" id="row-'+def.k+'"><span class="sdesc">'+esc(def.d)+'</span>'+ return '<div class="sub'+(on?'':' sdis')+'" id="row-'+def.k+'"><div style="flex:1 1 auto;min-width:0"><span class="sdesc">'+esc(def.d)+'</span>'+exImg+'</div>' +'<select data-sel="'+def.k+'" aria-label="'+esc(def.l)+'">'+def.sel.map(x=>'<option'+(st[def.k]===x?' selected':'')+'>'+x+'</option>').join('')+'</select></div>'; } const on=st[def.k]==='on';- return '<div class="srow"><div class="stxt"><div class="slbl">'+esc(def.l)+'</div><div class="sdesc">'+esc(def.d)+'</div></div>'+ const testRow=def.testflash?'<div class="sub"><span class="sdesc">'+((SNAP&&SNAP.counts&&SNAP.counts.windows)||0)+' pup window(s) open right now; their taskbar buttons will highlight</span>'+ +'<button class="btn sm" data-act="flash-all" title="orange-highlight every open pup taskbar button so you can see what this looks like">test highlight</button>'+ +'<button class="btn sm" data-act="unflash-all" title="clear every orange highlight">clear highlight</button></div>':'';+ return '<div class="srow"><div class="stxt"><div class="slbl">'+esc(def.l)+'</div><div class="sdesc">'+esc(def.d)+'</div>'+exImg+testRow+'</div>' +'<div class="tgl'+(on?' is-on':'')+'" role="switch" tabindex="0" aria-checked="'+on+'" aria-label="'+esc(def.l)+'" data-tgl="'+def.k+'"></div></div>'; }).join(''); }@@ -8553,6 +8561,7 @@ document.addEventListener('click',ev=>{ if(ev.target.closest('#dlgClose')||ev.target.id==='dlgBg'){_q('#dlgWrap').classList.add('hidden');return;} const t=ev.target.closest('[data-tgl]'); if(t){setSetting(t.dataset.tgl,t.classList.contains('is-on')?'off':'on');return;}+ if(ev.target.closest('[data-testcap]')){act({action:'test-caption'});return;} }); document.addEventListener('input',ev=>{ const r=ev.target.closest('[data-slider]');@@ -8691,6 +8700,12 @@ const server = http.createServer(async (req, res) => { try { await tab.page.close(); } catch (e) {} return done({ ok: true, closed: tid }); }+ if (act === 'test-caption') {+ // v1.9.262 (John: "give them a test button so they can see the caption").+ const ms = Math.max(500, Math.min(5000, Number(pupSettings().fgCaptionMs) || 3500));+ pupCaption(`This is what an AI-foreground caption looks like. Reason: "you clicked Test in pup settings" (${(ms / 1000).toFixed(1)}s)`, ms, 'pup-fg-reason', ms).catch(() => {});+ return done({ ok: true, ms });+ } if (act === 'set-setting') { // v1.9.259: the dashboard settings dialog. Whitelisted keys only; values validated. const ALLOWED = {@@ -8707,7 +8722,7 @@ const server = http.createServer(async (req, res) => { } else if (!ALLOWED[key] || !ALLOWED[key].includes(val)) { return done({ ok: false, error: 'unknown setting or value' }); }- savePupSettings({ [key]: val });+ savePupSettings(key === 'openFlash' ? { [key]: val, _openFlashChosen: true } : { [key]: val }); console.log(`[settings] "${key}" -> ${val} (dashboard dialog)`); return done({ ok: true, settings: pupSettings() }); }@@ -8820,6 +8835,17 @@ const server = http.createServer(async (req, res) => { res.end(buf); return; } catch (e) { res.writeHead(404); res.end(); return; } }+ if (pathname === '/dashboard/example') {+ // v1.9.262 (John: "show screenshots... so user knows what this is cuz they won't know what+ // aumid is"): illustrative images for the settings dialog, shipped with the bridge.+ try {+ const nm = (url.searchParams.get('n') || '').replace(/[^a-z]/g, '');+ const f = path.join(__dirname, 'icons', 'settings-ex', nm + '.png');+ const buf = fs.readFileSync(f);+ res.writeHead(200, { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=600' });+ res.end(buf); return;+ } catch (e) { res.writeHead(404); res.end(); return; }+ } if (pathname === '/dashboard/stampicon') { // v1.9.246 (John's audit): a STAMPED window's real taskbar button shows the icon the stamp // registered (pupIconPath's category tile), NOT the Chrome exe icon - the strip must match.