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
v2.0.270: Wrong taskbar ART that pup swore was correct (John's Bills window). Established by measurement: the AUMID was registered, its IconResource pointed at the right .ico, the .ico itself rendered the Bills name tile correctly at all four sizes, the button was correctly NAMED Bills, and a full threadTiles off/on re-register did not shift it — yet the shell drew the category tile. Cause: ab caches icon HANDLES by cacheKey for its process lifetime, and the thread-tile cacheKey versioned only on tile GENERATION, which changes only when we bump it. A handle cached from an earlier stamp (category art, before the tile finished rendering) therefore served forever. The cacheKey now keys on tile CONTENT (mtime+size), so any re-render is automatically a new handle and a stale one is structurally impossible. Also closes the audit's blind spot honestly: it infers art from _buttonBuiltAt vs tile mtime and cannot read the glass, so it reported CLEAN while John looked at a wrong icon. pup_audit_icons gains {force:true} to re-assert every icon with a fresh epoch regardless of findings, and the clean-verdict hint now says outright that a clean model is not proof of correct pixels.
4 files changed
+43−10
adom-bridge-puppeteer-manifest.json+5−5@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "2.0.269",- "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.269/adom-bridge-puppeteer-v2.0.269.zip",- "sha256": "79ea5e66d2ce1170a71ee750465a2a0dbe6e08e207b8b51f5f018f40539558de",- "size": 960487,+ "version": "2.0.270",+ "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.270/adom-bridge-puppeteer-v2.0.270.zip",+ "sha256": "f39d36e74bcbea81873697a7351f29ad5ad887c76cfa72f909771c4c1c55d789",+ "size": 961323, "verbPrefixes": [ "browser_", "desktop_recorder_",@@ -12,5 +12,5 @@ ], "healthEndpoint": "/status", "statusVerb": "browser_readiness",- "released_at": "2026-08-21T01:10:04.376863Z"+ "released_at": "2026-08-21T18:44:30.225037Z" }
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-2.0.269+2.0.270
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "2.0.269",+ "version": "2.0.270", "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. pup_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. pup_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, pup_alert_window posts a native toast whose click focuses the window, pup_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-bridge", "author": "Adom Inc.",
src/server.js+36−3@@ -4148,7 +4148,20 @@ async function stampPupIdentity(sessionId, opts) { // the default Chrome-for-Testing identity (measured: fusion-verb-tests/reclaimed, first seen // AFTER the BMP fix, branded fine; shotlog/sxsw, first seen before it, never did). Version // the key with the tile generation so new art always gets a fresh handle.- _cacheKey = `pup-thread-${threadSlug(_tn)}-${THREAD_TILE_GEN}-e${_regEpoch}`;+ // v2.0.270 (John: "why is this pup icon so wrong? its not showing the ai-thread name"):+ // versioning by GENERATION is not enough. ab caches icon HANDLES by cacheKey for its whole+ // process lifetime, and the generation only changes when WE bump it — so a button whose+ // handle ab cached from an earlier stamp (category art, before this thread's tile finished+ // rendering) keeps serving that dead handle forever. Measured on John's "Bills" window: the+ // AUMID was registered, its IconResource pointed at the right .ico, and the .ico itself+ // rendered "Bills" correctly at all four sizes — yet the shell drew the category tile, and a+ // full threadTiles off/on re-register did NOT shift it, because the cacheKey never changed.+ // Key on the tile's CONTENT instead: mtime+size means any re-render is automatically a new+ // handle, and a stale one is structurally impossible rather than something an audit has to+ // notice after the fact.+ let _tsig = '0';+ try { const _stt = fs.statSync(_tp); _tsig = `${Math.round(_stt.mtimeMs)}-${_stt.size}`; } catch (e) {}+ _cacheKey = `pup-thread-${threadSlug(_tn)}-${THREAD_TILE_GEN}-t${_tsig}-e${_regEpoch}`; } } if (_stampGen !== _identityGen) { console.log(`[identity] "${sessionId}" stamp ABORTED (identity generation moved: a mode switch superseded this in-flight stamp)`); return false; }@@ -5033,6 +5046,12 @@ foreach($bar in $bars){ $btns=$bar.FindAll([System.Windows.Automation.TreeScope] } async function auditTaskbarIcons(reason = 'manual', opts = {}) { const fix = opts.fix !== false;+ // v2.0.270: `force` re-asserts every button's ART even when nothing was detected. This exists+ // because the detector CANNOT see the glass — it infers art correctness from _buttonBuiltAt vs the+ // tile's mtime, and that inference was clean while John was staring at a wrong icon. When the+ // model and the pixels disagree, the model loses: force bumps the registration epoch (new cacheKey+ // -> ab must re-read the icon) and re-stamps every session.+ const force = !!opts.force; if (process.platform !== 'win32') return { ok: true, skipped: 'not win32' }; if (_auditBusy) return _lastAudit || { ok: true, skipped: 'audit already running' }; _auditBusy = true;@@ -5078,6 +5097,20 @@ async function auditTaskbarIcons(reason = 'manual', opts = {}) { } } let repaired = 0;+ if (force) {+ console.log(`[icon-audit] (${reason}) FORCE: re-asserting art on ${live.length} button(s) regardless of findings — the detector cannot read the glass, so a clean verdict is not proof.`);+ try { savePupSettings({ _stripEpoch: Number(pupSettings()._stripEpoch || 0) + 1 }); } catch (e) {}+ for (const [sid, sess] of live) {+ try {+ const appId = sess._curAppId;+ if (appId) { try { _registeredPupAumids.delete(appId); } catch (e) {} }+ sess._curAppId = null; sess._frozenAppId = null; sess._threadNudged = null;+ await stampPupIdentity(sid, { reason: 'icon-audit force' });+ if (sess._hwnd) nudgeTaskbarButton(sess._hwnd, sid);+ repaired++;+ } catch (e) {}+ }+ } if (fix && findings.length) { console.log(`[icon-audit] (${reason}) ${findings.length} finding(s): ${findings.map(f => f.sid + ':' + f.kind).join(', ')} — REPAIRING`); // force ab to re-read every icon: bump the registration epoch so cacheKeys change@@ -17786,9 +17819,9 @@ const server = http.createServer(async (req, res) => { case 'pup_audit_icons': { // v2.0.228: detect drift between pup's model and the SHELL's own buttons, repair it, and // report what was wrong. fix:false makes it a dry run.- const _r = await auditTaskbarIcons(args.reason || 'pup_audit_icons verb', { fix: args.fix !== false });+ const _r = await auditTaskbarIcons(args.reason || 'pup_audit_icons verb', { fix: args.fix !== false, force: !!args.force }); sendJSON(res, { success: true, audit: _r,- _hint: _r.ok ? 'Every live window matches the shell in this mode.'+ _hint: _r.ok ? 'Every live window matches the shell IN PUP\'S MODEL for this mode. That is not the same as matching the GLASS: the audit cannot read pixels, it infers art from timestamps. If a button looks wrong to a human, believe the human and re-run with {force:true}, which re-asserts every icon with a fresh cache key.' : `${_r.findings.length} drift finding(s), ${_r.repaired} repaired — re-run in ~30s to confirm the glass settled.` }); return; }