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
AUMID REVERTED + the dashboard stops lying about taskbar match (two John callouts). (1) THE AUMID RELAPSE: v1.9.245 re-enabled per-window AUMID stamping off an ambiguous feasibility question, silently reversing the settled 2026-07-29 no-AUMID decision that took two days of pain to reach (minimal-touch measured verdict: 'not branding the icons is making pup perform better'; overlays yes, AUMIDs no). John: 'we just spent 2 days having you stop using aumid and you just started using aumid again?' REVERTED in full: the stamp gate is absolute again under minimal-touch (no once escape hatch), the create-path stamp is re-gated, the enforcer stamping is removed. Windows stamped during the relapse keep their buttons until they close (the AUMID property lives on the window; un-stamping would destroy buttons again) and the dashboard models them truthfully as legacy. The complete decision history, the Windows facts, and the process rule for ever revisiting this (name the decision being reversed + get explicit consent FIRST; a question is not a mandate) are recorded in the new dev-skills/pup-aumid-decisions/SKILL.md. (2) THE LYING LED (John: 'your dashboard view of the windows taskbar icons is not accurate to what is showing in the taskbar and yet you have a green led indicating it is'): the MATCH light was a model-consistency check dressed as a glass-consistency check - it never looked for real taskbar buttons pup is not modeling at all. Windows offers no read-back API for button art, but pup CAN catch the worst lie: the snapshot now enumerates every visible window of pup-managed Chrome processes (cached 12s) and diffs against tracked session hwnds; any untracked window renders as a dashed ? ghost button in the strip, forces the header LED red with 'N UNTRACKED', and its tooltip says exactly why. The LED semantics + never-overclaim rule are recorded in the dev skill. Also purged a pre-existing em-dash from the strip tooltip.
4 files changed
+42−35
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.9.247",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.247/adom-bridge-puppeteer-v1.9.247.zip",- "sha256": "2a0d2d08e1a8360ccdc287cf8a5a079eb33bed4c90d2f611eff3eef231c689f5",- "size": 467827,+ "version": "1.9.248",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.248/adom-bridge-puppeteer-v1.9.248.zip",+ "sha256": "80e1f25941d471d226de6cbf5dd2e4737ac77086fa1f0801955ef670db37645b",+ "size": 468061, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.247+1.9.248
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.9.247",+ "version": "1.9.248", "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+36−29@@ -2884,17 +2884,12 @@ async function stampPupIdentity(sessionId, opts) { // v1.9.185 minimal-touch: the AUMID stamp is the single most invasive thing pup does to a window // it does not own (Windows DESTROYS and RECREATES the taskbar button on an AppUserModelID change). // Gated at the choke point so no caller can leak it.- // v1.9.245 (John: "when i say open a window i generally want them as separate taskbar icons"):- // {once:true} callers may stamp UNDER minimal-touch too — but exactly ONE gentle stamp per window,- // at creation (or the enforcer's one-time catch-up), never the category re-stamp churn that made- // the old machinery invasive. One stamp = one button recreation = each window gets its OWN taskbar- // icon, which is what he wants; the shared adom-you profile is irrelevant because window identity- // is a per-HWND property, not per-process.- if (minimalTouch()) {- if (!(opts && opts.once)) return false;- const _s0 = sessions.get(sessionId);- if (_s0 && _s0._curAppId) return true; // once means ONCE- }+ // v1.9.248 REVERTED: v1.9.245 re-enabled stamping here off an ambiguous question, silently+ // reversing the settled 2026-07-29 NO-AUMID decision (John: "we just spent 2 days having you stop+ // using aumid and you just started using aumid again?"). The decision STANDS: overlays only,+ // no identity stamps, in any mode. Full history + the process rule for ever revisiting this:+ // dev-skills/pup-aumid-decisions/SKILL.md. Do NOT add an escape hatch here again.+ if (minimalTouch()) return false; if (process.platform !== 'win32' || !sessionId || _identitySupported === false) return false; if (!_identityRegistered) { await ensurePupIdentityRegistered(); if (_identitySupported === false) return false; } try {@@ -6836,7 +6831,7 @@ async function launchSession(sessionId, url, { freshProfile = false, profile, st // v1.9.185 minimal-touch: do NOT stamp a per-session AUMID. Changing a window's AppUserModelID // makes Windows DESTROY and RECREATE its taskbar button — the single most invasive thing pup did // to a window it does not own. In minimal mode the window keeps Chrome's own identity.- if (process.platform === 'win32') { stampPupIdentity(sessionId, { once: true }).catch(() => {}); }+ if (process.platform === 'win32' && !minimalTouch()) { stampPupIdentity(sessionId).catch(() => {}); } // v1.8.68: the park is DEFINED here but KICKED OFF only after the real URL's navigation has // SETTLED (below). Rationale: the first cross-origin paint RAISES the window; if the window@@ -8088,11 +8083,10 @@ async function dashboardSnapshot() { // 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.- // v1.9.245 (John: 'when i say open a window i generally want them as separate taskbar icons'):- // pup now stamps each window ONCE at creation (per-HWND identity, works fine on the shared- // profile), so a STAMPED window has its OWN real taskbar button; only unstamped windows (stamp- // pending/failed, e.g. PDF viewers with no title tag) share the process button. This mirrors- // exactly what Windows does, which is the whole point of the mimic.+ // v1.9.248: AUMID stamping is REVERTED (settled no-AUMID decision; see+ // dev-skills/pup-aumid-decisions). This branch remains ONLY to truthfully model windows that+ // were stamped before the revert - the AUMID property lives on the window until it closes, so+ // those really do keep their own taskbar buttons. New windows never enter it. let baseIcon; if (w._curAppId) { key = 'aumid:' + w._curAppId;@@ -8125,6 +8119,23 @@ async function dashboardSnapshot() { : leds.includes('amber') ? 'all painted, but a window fell back to the generic icon (page had no favicon)' : 'every window wears its real page-favicon overlay — dashboard matches the taskbar'; }+ // v1.9.248 (John: "your dashboard view of the windows taskbar icons is not accurate to what is+ // showing in the taskbar and yet you have a green led indicating it is. so you are still lying to+ // me"). He is right: the LED asserted "matches the taskbar" while only checking pup's own+ // bookkeeping. Windows offers no API to read BACK a button's overlay, but pup CAN catch the worst+ // lie: real taskbar buttons from pup-managed Chrome processes that pup has NO session for (zombie+ // windows from lost sessions - his two mystery teal tiles). Enumerate (cached 12s) and surface+ // them as untracked; the header can only claim MATCH when there are none.+ let untracked = [];+ try {+ const nowT = Date.now();+ if (!dashboardSnapshot._enumAt || nowT - dashboardSnapshot._enumAt > 12000) {+ dashboardSnapshot._enumAt = nowT;+ dashboardSnapshot._enumHwnds = await Promise.race([enumAllPupChromeHwnds(), new Promise(r => setTimeout(() => r(null), 9500))]) || dashboardSnapshot._enumHwnds || [];+ }+ const known = new Set(wins.map(w => Number(w.hwnd)).filter(Boolean));+ untracked = (dashboardSnapshot._enumHwnds || []).filter(h => !known.has(Number(h)));+ } catch (e) {} const threads = [...new Set(wins.map(w => w.owner || w.ownerThread).filter(Boolean))].sort(); const profiles = {}; for (const w of wins) profiles[w.profile] = (profiles[w.profile] || 0) + 1;@@ -8135,6 +8146,7 @@ async function dashboardSnapshot() { counts: { windows: wins.length, tabs: wins.reduce((a, w) => a + w.tabCount, 0), browsers: browsers.size, threads: threads.length }, threads, profiles, taskbarButtons: [...buttons.values()],+ untrackedWindows: untracked, windows: wins.sort((a, b) => (b.openedAt || 0) - (a.openedAt || 0)), }; }@@ -8168,6 +8180,7 @@ select,input{background:#111b23;color:#e6edf3;border:1px solid #23303c;border-ra #taskbar{position:sticky;top:49px;z-index:9;display:flex;gap:6px;align-items:center;padding:8px 18px;background:#0a0f14;border-bottom:1px solid #1d2833;overflow-x:auto} #taskbar .lbl{color:#5c6c7a;font-size:11px;margin-right:6px;flex:0 0 auto} .tbi{position:relative;width:48px;height:42px;border-radius:8px;background:#0f1720;border:1px solid #1d2833;display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto}+.tbi.ghost{color:#5c6c7c;font-size:18px;font-weight:700;border-style:dashed;border-color:#7c3a3a} .tbi:hover{border-color:#12e0c4;background:#12202a} .tbi.fg{border-color:#12e0c4} .tbi img.base{width:30px;height:30px;object-fit:contain}@@ -8269,9 +8282,10 @@ function render(){ _q('#cM').style.display=d.bridge.minimalTouch?'':'none'; // header overlay-match rollup: red if ANY button bare, amber if any generic, else green const bl=(d.taskbarButtons||[]).map(b=>b.led||'red');- const roll=bl.includes('red')?'red':bl.includes('amber')?'amber':(bl.length?'green':'red');+ let roll=bl.includes('red')?'red':bl.includes('amber')?'amber':(bl.length?'green':'red');+ if((d.untrackedWindows||[]).length) roll='red'; // an unmodeled real button = the mimic CANNOT claim MATCH _q('#cLedDot').className='led '+roll;- _q('#cLedTxt').textContent=roll==='green'?'MATCH':roll==='amber'?'generic':'MISMATCH';+ _q('#cLedTxt').textContent=(d.untrackedWindows||[]).length?((d.untrackedWindows||[]).length+' UNTRACKED'):roll==='green'?'MATCH':roll==='amber'?'generic':'MISMATCH'; _q('#age').textContent='updated '+new Date(d.ts).toLocaleTimeString(); const sel=_q('#f'),cur=sel.value; if(sel.options.length-1!==d.threads.length){sel.innerHTML='<option value="">all AI threads</option>'+d.threads.map(t=>'<option>'+esc(t)+'</option>').join('');sel.value=cur;}@@ -8285,15 +8299,16 @@ function render(){ // BASE icon full-size, with the painted overlay badge in the TOP-RIGHT corner at ~55% (measured // from his actual taskbar), and NO overlay at all when pup has not painted one (a bare Chrome // button must render bare here too, otherwise the mimic lies).+ const ghosts=(d.untrackedWindows||[]).map(h=>'<div class="tbi ghost" title="a window of the pup-managed Chrome is on the real taskbar but pup has NO session for it (hwnd '+h+'). The mimic cannot know its icon - this is why the header LED is red.">?<span class="led red"></span></div>').join(''); _q('#taskbar').innerHTML='<span class="lbl">TASKBAR</span>'+groups.map((g,i)=>{ const fg=g.wins.some(w=>w.foreground);- return '<div class="tbi'+(fg?' fg':'')+'" data-jump="g'+i+'" title="'+esc(g.label)+' — '+esc(g.ledWhy||'')+'">'+ return '<div class="tbi'+(fg?' fg':'')+'" data-jump="g'+i+'" title="'+esc(g.label)+': '+esc(g.ledWhy||'')+'">' +'<img class="base" src="'+esc(g.baseIcon||'')+'" data-fallback="hide">' +(g.badgeSession?'<img class="ov" src="'+badge(g.badgeSession)+'" data-fallback="hide">':'') +'<span class="led '+esc(g.led||'red')+'" title="'+esc(g.ledWhy||'')+'"></span>' +(g.wins.length>1?'<span class="n">'+g.wins.length+'</span>':'') +(fg?'<span class="u"></span>':'')+'</div>';- }).join('');+ }).join('')+ghosts; // tree: taskbar icon -> windows -> tabs _q('#m').innerHTML=groups.length?groups.map((g,gi)=>{@@ -12333,14 +12348,6 @@ server.listen(PORT, BIND_HOST, async () => { try { for (const [sid2, s2] of [...sessions]) { if (s2._lostBrowser || !(s2.tabs || []).length) continue;- // v1.9.245: per-window taskbar identity is enforced like the overlay — a window without its- // one-time stamp gets it here (covers windows opened before this build / restart recovery).- if (!s2._curAppId && Date.now() - (s2._stampTriedAt || 0) > 45000) {- // retry on a cooldown, not once-ever: the create-time attempt can race the title tag- // (a fresh window is unfindable for a beat) and a single lifetime try left such windows- // permanently unstamped (audit2-hn, caught in John's audit round 2).- s2._stampTriedAt = Date.now(); stampPupIdentity(sid2, { once: true }).catch(() => {}); continue;- } const bare = !s2._appOverlay; const stuckGeneric = s2._appOverlay && s2._overlayIsGeneric; if (!bare && !stuckGeneric) continue;