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
Thread Tiles: brand the taskbar button reliably + add an AUMID janitor. Write the per-thread AUMID registry IconResource even under minimal-touch so the thread-name tile wins over the default pup icon, and add a janitor plus uninstall cleanup that reap orphaned Adom.Pup registry keys and prior-generation tile icon files.
2 files changed
+71−16
adom-bridge-puppeteer-manifest.json+2−2@@ -3,8 +3,8 @@ "name": "puppeteer", "version": "2.0.161", "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.161/adom-bridge-puppeteer-v2.0.161.zip",- "sha256": "93e2695653173f79f7fd596cb2ecb1c9098e5d6a296681948b9521a903367c28",- "size": 908047,+ "sha256": "cf373fcc082cf16d27cab394fe9cff5ad20669f84490f20e8fb1ce362f8e95f4",+ "size": 909406, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/server.js+69−14@@ -4383,36 +4383,70 @@ async function renderThreadTileIco(page, name) { // <sid> is not a live session. Never touch the pinnable base id. function pruneStaleAumids() { if (process.platform !== 'win32') return;+ // v2.0.161 — rewritten to key off the LIVE appIds directly, across EVERY AUMID shape+ // (per-session Adom.Pup.<sid>.<cat>.<gen>, category-group Adom.Pup.<cat>.<gen>, dashboard+ // Adom.Pup.Dashboard.<gen>, and THREAD TILES Adom.Pup.thread.<slug>.<gen>). The old code derived+ // a <sid> by stripping known suffixes and kept a key iff that sid was a live session — which+ // BROKE thread tiles: a thread key strips to "thread.<slug>.<gen>" (never a session id), so a+ // perfectly live thread's key looked orphaned and got deleted on every sweep, un-branding the+ // taskbar button. Instead: gather every appId any live session is (or is about to be) stamped+ // with — _curAppId (live), _frozenAppId (frozen), _desiredAppId (mid-open, set before the stamp+ // lands), the pre-registration _earlyStamped map, and _registeredPupAumids — and delete only the+ // Adom.Pup.* keys NOT in that set. The pinnable base launcher (Adom.Pup) always stays.+ const liveAppIds = new Set([PUP_APP_ID]);+ for (const [, s] of sessions) {+ for (const id of [s && s._curAppId, s && s._frozenAppId, s && s._desiredAppId]) { if (id) liveAppIds.add(id); }+ }+ for (const id of _earlyStamped.values()) { if (id) liveAppIds.add(id); }+ for (const id of _registeredPupAumids) { if (id) liveAppIds.add(id); } try { const out = execFileSync('reg', ['query', 'HKCU\\Software\\Classes\\AppUserModelId'], { encoding: 'utf8', timeout: 8000, windowsHide: true });- const live = new Set(sessions.keys()); let dropped = 0; for (const line of out.split(/\r?\n/)) {- const m = line.match(/\\(Adom\.Pup\.(.+))$/);+ const m = line.match(/\\(Adom\.Pup(?:\..+)?)\s*$/); if (!m) continue;- const appId = m[1], rest = m[2];- if (appId === PUP_APP_ID) continue; // the pinnable launcher stays- const sid = rest.replace(/\.i\d+$/, '').replace(/\.(wiki-in|wiki|app|web|mixed|plain)$/, '');- // v1.9.189 (John's taskbar audit): a LIVE session can own SEVERAL keys, because every category- // change registered a NEW AUMID and nothing removed the previous one (audit found one window- // holding 3: .kimi-about-mockup, .kimi-about-mockup.app.i3, .kimi-about-mockup.plain.i3). Keep- // only the key the session is CURRENTLY stamped with; drop its superseded ones.- if (live.has(sid)) {- const cur = (sessions.get(sid) || {})._curAppId;- if (!cur || cur === appId) continue; // current key (or unknown) — keep- }+ const appId = m[1];+ if (appId === PUP_APP_ID || liveAppIds.has(appId)) continue; // launcher + in-use keys stay try { execFileSync('reg', ['delete', `HKCU\\Software\\Classes\\AppUserModelId\\${appId}`, '/f'], { encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: 'ignore' }); dropped++; } catch (e) {} }- if (dropped) console.log(`[identity] pruned ${dropped} stale Adom.Pup.* AUMID key(s) from HKCU`);+ if (dropped) console.log(`[identity] pruned ${dropped} stale Adom.Pup.* AUMID key(s) from HKCU (kept ${liveAppIds.size - 1} live)`); } catch (e) {}+ // The .ico tiles those keys pointed at also accumulate in %TEMP% across generation bumps+ // (t1→t2→t3, i6→i7). Keep ONLY the current-generation dir under each cache root; every sibling is+ // a prior gen no live key references. Cheap readdir, so it rides the same janitor cadence.+ pruneStaleIconGenDirs();+}+// v2.0.161 (John: "you need a janitor AND uninstall cleanup for the aumid icons you register"):+// reap prior-generation icon-cache directories so tile .ico files never pile up in %TEMP%.+function pruneStaleIconGenDirs() {+ const roots = [+ { dir: path.dirname(THREAD_TILE_DIR), keep: path.basename(THREAD_TILE_DIR) }, // pup-thread-tiles/<gen>+ { dir: path.dirname(_NOTCH_CACHE), keep: path.basename(_NOTCH_CACHE) }, // pup-notched-icons/<gen>+ ];+ for (const { dir, keep } of roots) {+ try {+ if (!fs.existsSync(dir)) continue;+ for (const name of fs.readdirSync(dir)) {+ if (name === keep) continue;+ try { fs.rmSync(path.join(dir, name), { recursive: true, force: true, maxRetries: 2, retryDelay: 200 }); } catch (e) {}+ }+ } catch (e) {}+ } } async function unregisterPupAumid(appId) { if (process.platform !== 'win32' || !appId || appId === PUP_APP_ID) return;+ // v2.0.161 THREAD TILES: a thread AUMID (Adom.Pup.thread.<slug>.<gen>) is SHARED by every window+ // of that thread, so closing ONE window must NOT unregister the key the siblings still wear —+ // that would un-brand their taskbar button and drop their jump list. Bail if any OTHER live+ // session is still stamped with this appId. (Harmless for unique per-session ids: none will match.)+ for (const [, s] of sessions) {+ if (s && (s._curAppId === appId || s._frozenAppId === appId || s._desiredAppId === appId)) return;+ } _registeredPupAumids.delete(appId); // v1.9.52: CLEAR this AUMID's jump list before unregistering it. Otherwise its CustomDestinations // file lingers in the shell (measured: 6 stale jump-list files for 3 live windows, because every@@ -4422,6 +4456,14 @@ async function unregisterPupAumid(appId) { try { await chrome.abCommand('desktop_set_window_jumplist', { appId, tasks: [] }, { timeoutMs: 5000 }); } catch (e) {} } try { await chrome.abCommand('desktop_unregister_app_identity', { appId }, { timeoutMs: 5000 }); } catch (e) {}+ // v2.0.161: also drop the HKCU IconResource key DIRECTLY. Under minimal-touch we write that key via+ // reg.exe (AD's register roundtrip is skipped), so AD's unregister above may leave it behind — delete+ // it here so a closed window's key never lingers until the 5-min janitor. reg.exe is idempotent+ // (a missing key just errors, swallowed). The sibling-guard above already protects shared thread keys.+ try {+ execFileSync('reg', ['delete', `HKCU\\Software\\Classes\\AppUserModelId\\${appId}`, '/f'],+ { encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: 'ignore' });+ } catch (e) {} } // v1.9.320 (John: "i turned OFF jump lists in settings but i still see them in the taskbar. did you // ralph test this at all?"). No — I only tested ON. Toggling OFF never CLEARED the already-committed@@ -13085,6 +13127,11 @@ const server = http.createServer(async (req, res) => { { path: P('pup-annotate-extension'), label: 'annotate extension' }, { path: P('pup-avatars'), label: 'pup-avatars' }, { path: path.join(require('os').tmpdir(), 'pup-badge-debug'), label: 'badge-debug temp' },+ // v2.0.161: the AUMID icon caches (ALL generations) — thread-name tiles + notched category+ // tiles the taskbar keys pointed at. The registry keys are wiped below; wipe their .ico+ // files too so uninstall leaves zero pup footprint in %TEMP%.+ { path: path.join(require('os').tmpdir(), 'pup-thread-tiles'), label: 'thread-tile icons (AUMID)' },+ { path: path.join(require('os').tmpdir(), 'pup-notched-icons'), label: 'category tile icons (AUMID)' }, ]; const fileTargets = [ { path: PUP_SETTINGS_PATH, label: 'pup-settings.json' },@@ -17031,6 +17078,14 @@ const _onPupBound = async () => { // v1.9.279: the reconciler runs UNGATED (bookkeeping + identity only, never a process kill), so // closed windows are reaped and stamp/settings drift is corrected within seconds, in every mode. setInterval(() => { reconcileModel('periodic').catch(() => {}); }, 8000);+ // v2.0.161 (John: AUMID icons need a JANITOR, not just close-time cleanup): sweep orphaned+ // Adom.Pup.* registry keys + prior-generation .ico caches on a slow cadence, so leaks from+ // generation bumps, renamed/closed threads, and crashed windows never accumulate over a long+ // uptime. The 60s startup prune skips an empty session map (boot-recovery guard); this steady-state+ // sweep runs every 5 min and reaps even when nothing is live (an empty map long after boot means+ // the keys are genuinely orphaned, and a re-stamp self-heals the rare reclaim race). reg.exe is+ // only spawned every 5 min, so it adds no ab-log noise.+ setInterval(() => { try { pruneStaleAumids(); } catch (e) {} }, 300000); // Build the notched category tiles once at boot (John's clean-backdrop-for-the-overlay idea). // Fire-and-forget: pupIconPath serves the un-notched tile until this finishes, then it re-stamps. ensureNotchedIcons().catch(() => {});