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
Plain mode now converges every Chrome for Testing window onto Chrome's own native per-profile AUMID instead of wiping identities to empty: Chrome lazily re-asserts its native id on windows, so wiped windows and re-tagged windows drifted into two separate taskbar groups both labeled Google Chrome for Testing, and the convergence approach is idempotent against Chrome's re-assertions so the group can never split again; the fix also covers orphan windows pup no longer manages, corrects a 16-byte PROPVARIANT marshaling bug that made property reads silently return zeros, and remembers the learned native id across restarts.
4 files changed
+79−28
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "2.0.220",- "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.220/adom-bridge-puppeteer-v2.0.220.zip",- "sha256": "387667370a80416d33986ea2d4ddc3d9eceb8346b65a3ba94588efa665bded2a",- "size": 940945,+ "version": "2.0.221",+ "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.221/adom-bridge-puppeteer-v2.0.221.zip",+ "sha256": "df7313dd2418a792e75d4e452366eca3a683f634ef906529a0d5ba1017a81079",+ "size": 942261, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-2.0.220+2.0.221
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "2.0.220",+ "version": "2.0.221", "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+73−22@@ -4602,42 +4602,93 @@ async function stripAllPupIdentities(reason) { try { if (!s._hwnd) await resolveSessionHwndByBounds(s, sid); } catch (e) {} if (s._hwnd) hwnds.push(Number(s._hwnd)); }- if (hwnds.length) {- _nudgeUntil = Date.now() + 8000;- const ps = `$code=@'-using System; using System.Runtime.InteropServices;-public static class Wipe {+ {+ // v2.0.221 (John's two-CfT-buttons find): wiping AUMIDs to VT_EMPTY was WRONG. Chrome sets its+ // own per-profile AUMID on every window (ChromeForTestin<hash>.<profile>.Default) and lazily+ // RE-ASSERTS it — so wiped windows grouped under the exe-fallback identity while Chrome-retagged+ // ones grouped under the native id: TWO taskbar buttons, both labeled "Google Chrome for+ // Testing", drifting further apart over time (an Explorer restart rebuilds the same split).+ // Truly plain = CONVERGE every CfT window onto Chrome's OWN native AUMID (idempotent against+ // Chrome's re-assertions), clear pup's Relaunch trio, null pup's WM_SETICON icons, nudge.+ // Covers ALL windows of pup's browser processes — orphans included — not just live sessions.+ // NOTE the correct 24-byte PROPVARIANT: the old 16-byte explicit-layout struct made GetValue+ // silently return zeros, which is why the wipe path never saw Chrome's ids in the first place.+ _nudgeUntil = Date.now() + 12000;+ const _pids = new Set();+ for (const [, s9] of [...sessions]) { try { const pid = Number(macSessionPid(s9) || 0); if (pid) _pids.add(pid); } catch (e) {} }+ try { for (const [, be] of browsers) { const b = be && be.browser; const pid = Number((b && (b._detachedPid || (b.process() && b.process().pid))) || 0); if (pid) _pids.add(pid); } } catch (e) {}+ const _fallbackNative = String(pupSettings()._cftNativeAumid || '');+ const psBody = `$code=@'+using System; using System.Text; using System.Runtime.InteropServices;+public static class Conv { [StructLayout(LayoutKind.Sequential)] public struct PROPERTYKEY { public Guid fmtid; public uint pid; }- [StructLayout(LayoutKind.Explicit)] public struct PROPVARIANT { [FieldOffset(0)] public ushort vt; [FieldOffset(8)] public IntPtr p; }+ [StructLayout(LayoutKind.Sequential)] public struct PROPVARIANT { public ushort vt; public ushort r1; public ushort r2; public ushort r3; public IntPtr p; public IntPtr p2; } [ComImport, Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IPropertyStore { int GetCount(out uint c); int GetAt(uint i, out PROPERTYKEY pk); int GetValue(ref PROPERTYKEY key, out PROPVARIANT pv); int SetValue(ref PROPERTYKEY key, ref PROPVARIANT pv); int Commit(); } [DllImport("shell32.dll")] public static extern int SHGetPropertyStoreForWindow(IntPtr hwnd, ref Guid iid, out IPropertyStore pps);+ [DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc cb, IntPtr lp);+ public delegate bool EnumProc(IntPtr h, IntPtr lp);+ [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h);+ [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int cmd); [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr h, uint m, IntPtr w, IntPtr l);- public static void Strip(long h) {- IntPtr hwnd = (IntPtr)h;- Guid iid = new Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"); IPropertyStore ps;- if (SHGetPropertyStoreForWindow(hwnd, ref iid, out ps) == 0) {- Guid AUM = new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3");- foreach (uint pid in new uint[]{2,3,4,5}) {- PROPERTYKEY k = new PROPERTYKEY(); k.fmtid = AUM; k.pid = pid;- PROPVARIANT pv = new PROPVARIANT(); pv.vt = 0;- ps.SetValue(ref k, ref pv);- }- ps.Commit();+ static Guid AUM = new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3");+ static Guid IID = new Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99");+ public static string Read(IntPtr hwnd) {+ IPropertyStore ps; if (SHGetPropertyStoreForWindow(hwnd, ref IID, out ps) != 0) return null;+ PROPERTYKEY k = new PROPERTYKEY(); k.fmtid = AUM; k.pid = 5;+ PROPVARIANT pv; if (ps.GetValue(ref k, out pv) != 0) return null;+ return (pv.vt == 31 && pv.p != IntPtr.Zero) ? Marshal.PtrToStringUni(pv.p) : null;+ }+ public static void Apply(IntPtr hwnd, string id) {+ IPropertyStore ps; if (SHGetPropertyStoreForWindow(hwnd, ref IID, out ps) != 0) return;+ foreach (uint pid in new uint[]{2,3,4}) {+ PROPERTYKEY k = new PROPERTYKEY(); k.fmtid = AUM; k.pid = pid;+ PROPVARIANT em = new PROPVARIANT(); em.vt = 0; ps.SetValue(ref k, ref em);+ }+ PROPERTYKEY k5 = new PROPERTYKEY(); k5.fmtid = AUM; k5.pid = 5;+ if (id != null && id.Length > 0) {+ PROPVARIANT pv = new PROPVARIANT(); pv.vt = 31; pv.p = Marshal.StringToCoTaskMemUni(id);+ ps.SetValue(ref k5, ref pv); ps.Commit(); Marshal.FreeCoTaskMem(pv.p);+ } else {+ PROPVARIANT em2 = new PROPVARIANT(); em2.vt = 0; ps.SetValue(ref k5, ref em2); ps.Commit(); } SendMessage(hwnd, 0x0080, (IntPtr)0, IntPtr.Zero); SendMessage(hwnd, 0x0080, (IntPtr)1, IntPtr.Zero);- if (GetForegroundWindow() != hwnd) { ShowWindow(hwnd, 0); System.Threading.Thread.Sleep(80); ShowWindow(hwnd, 4); } } }-'@; Add-Type -TypeDefinition $code; @(${hwnds.join(',')}) | ForEach-Object { [Wipe]::Strip($_) }; 'STRIPPED ${hwnds.length}'`;+'@+Add-Type -TypeDefinition $code+$targets = @(PIDS_CSV)+$script:wins = @()+[Conv]::EnumWindows({ param($h,$lp)+ if([Conv]::IsWindowVisible($h)){ $p2=0; [void][Conv]::GetWindowThreadProcessId($h,[ref]$p2); if($targets -contains [int]$p2){ $script:wins += $h } }; $true }, [IntPtr]::Zero) | Out-Null+$native = $null+foreach($h in $script:wins){ $a=[Conv]::Read($h); if($a -and $a.StartsWith("ChromeForTestin")){ $native=$a; break } }+if(-not $native){ $native = "FALLBACK_NATIVE" }+if($native -eq ""){ $native = $null }+$fg=[Conv]::GetForegroundWindow(); $n=0+foreach($h in $script:wins){+ $cur=[Conv]::Read($h)+ if($cur -ne $native){+ [Conv]::Apply($h,$native); $n+++ if($h -ne $fg){ [void][Conv]::ShowWindow($h,0); Start-Sleep -Milliseconds 80; [void][Conv]::ShowWindow($h,4) }+ }+}+Write-Output ("NATIVE: {0}" -f $(if($native){$native}else{"(none - emptied)"}))+Write-Output ("CONVERGED {0} of {1}" -f $n, $script:wins.Count)`;+ const psFinal = psBody.replace('PIDS_CSV', [..._pids].join(',') || '0').replace('FALLBACK_NATIVE', _fallbackNative.replace(/"/g, '')); try {- execFile('powershell', ['-NoProfile', '-Command', ps], { windowsHide: true, timeout: 30000 }, (err, stdout) => {- console.log(`[identity] plain-mode property wipe: ${err ? 'ERR ' + err.message.slice(0, 80) : String(stdout || '').trim()}`);+ const _tmpPs = path.join(os.tmpdir(), 'pup-plain-converge.ps1');+ fs.writeFileSync(_tmpPs, psFinal);+ execFile('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', _tmpPs], { windowsHide: true, timeout: 45000 }, (err, stdout) => {+ const out = String(stdout || '').trim();+ console.log(`[identity] plain converge: ${err ? 'ERR ' + err.message.slice(0, 80) : out.replace(/\n/g, ' | ')}`);+ const m = out.match(/NATIVE: (ChromeForTestin\S+)/);+ if (m) { try { savePupSettings({ _cftNativeAumid: m[1] }); } catch (e) {} } });- } catch (e) { console.log(`[identity] plain wipe spawn failed: ${e.message}`); }+ } catch (e) { console.log(`[identity] plain converge spawn failed: ${e.message}`); } } console.log(`[identity] stripped ${ids.size} registered identit(ies), wiping window props on ${hwnds.length} window(s) (${reason})`); // v2.0.200 (#53, reproduced twice in six-state proofs): sessions whose hwnd was unresolved at