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
1.8.73: Adom Pup window identity via AD 1.9.134 verbs — self-registration, create-time stamp, badge fallback
6 files changed
+102−12
CHANGELOG.md+20@@ -1,5 +1,25 @@ # Changelog — Adom Desktop Puppeteer Bridge +## 1.8.73 — 2026-07-18 — Adom Pup window identity: full icon/name takeover (AD ≥1.9.134)++pup windows now read "Adom Pup" with the Adom icon in the taskbar, alt-tab, and hover — no Chrome+logo, no CfT "Test" tile. Built on AD 1.9.134's verbs (shipped to pup's spec after the 2026-07-18+prototype):++- **Self-registration at bridge start** (`desktop_register_app_identity`, loopback direct-API):+ HKCU AUMID `Adom.Pup` + a Start Menu "Adom Pup" shortcut whose PIN launches a REAL managed pup+ window via adom-desktop-cli (the pinning trap, solved). The bridge owns its branding: the icon+ ships in this release zip (`icons/adom-pup.ico`; art changes require a NEW filename — Windows+ caches decoded icons by path).+- **Per-window stamp at CREATE** (`desktop_set_window_identity` with `cacheKey`), not just at+ park — identity is per-HWND, and stamping late would leave the window reading "Chrome for+ Testing" through its whole load. Re-stamped at park/heal/tab hot paths.+- **Overlay badge demoted to fallback**: on AD <1.9.134 (unknown verbs → self-gating) the teal+ overlay badge continues exactly as before; on capable AD the full identity replaces it.+ `desktop_taskbar` remains in use for flash/progress.+- Mechanical root (from the prototype): icon HICONs are process-owned USER objects — AD holds+ them in a process-lifetime cache, which is why AD-core is the only correct owner.+ ## 1.8.72 — 2026-07-18 — THE DEMARCATION GATE: no silent branded fallback, ever 1.8.71's "converge by the second session" still ran the FIRST session on branded Chrome — and the
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.8.72",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.72/adom-bridge-puppeteer-v1.8.72.zip",- "sha256": "1af978937b836a19fd200f92611e14293dce1d7a488e688242bf19a103e79d88",- "size": 176771,+ "version": "1.8.73",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.73/adom-bridge-puppeteer-v1.8.73.zip",+ "sha256": "cb40800ddefb3842fb9e3796088fc44f85efef8420c381f2c56546ee87ce9733",+ "size": 186537, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.8.72+1.8.73
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.8.72",+ "version": "1.8.73", "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.", "homepage": "https://wiki.adom.inc/adom/adom-desktop", "author": "Adom Inc.",
src/icons/adom-pup.icoadded⋯ 1 unchanged line ⋯
src/server.js+76−6@@ -1575,6 +1575,66 @@ function pupBadgeB64() { catch (e) { _pupBadgeB64 = ''; } return _pupBadgeB64; }+// v1.8.73: FULL WINDOW-IDENTITY TAKEOVER (AD >=1.9.134, built to pup's spec). Two halves:+// registration (once per bridge run: HKCU AUMID + Start Menu shortcut whose pin launches a REAL+// pup window via adom-desktop-cli) and per-window stamping (AUMID + WM_SETICON, done by AD-core+// because icon HICONs are process-owned USER objects that must outlive the caller — AD holds+// them in a process-lifetime cache; see dev skill "Full icon/identity takeover"). The bridge+// registers ITSELF over the loopback direct-API (trusted transport, Tier-2 verb is ungated+// there) and owns its icon in its install dir — change branding = ship a new pup release with a+// NEW icon FILENAME (Windows caches decoded icons by path). Self-gating: on AD <1.9.134 the+// verbs are unknown -> _identitySupported=false -> the overlay badge fallback keeps working.+const PUP_APP_ID = 'Adom.Pup';+const PUP_DISPLAY_NAME = 'Adom Pup';+let _identitySupported = null; // null = unprobed+let _identityRegistered = false;+function pupIconPath() { return path.join(__dirname, 'icons', 'adom-pup.ico'); }+function _isUnknownVerb(r, raw) { return /unknown (desktop )?command|unknown verb/i.test(((r && r.error) || '') + ' ' + (raw || '')); }+async function ensurePupIdentityRegistered() {+ if (process.platform !== 'win32' || _identitySupported === false) return false;+ if (_identityRegistered) return true;+ try {+ const r = await chrome.adCommand('desktop_register_app_identity', {+ appId: PUP_APP_ID, displayName: PUP_DISPLAY_NAME, iconPath: pupIconPath(),+ args: 'browser_open_window {"sessionId":"pinned","owner":"pinned-launch","url":"https://wiki.adom.inc"}',+ }, { timeoutMs: 8000 });+ const raw = r && (typeof r.output === 'string' ? r.output : JSON.stringify(r.output || ''));+ if ((r && r.success === false) || _isUnknownVerb(r, raw)) {+ if (_isUnknownVerb(r, raw)) { _identitySupported = false; console.log('[identity] AD lacks register_app_identity (needs >=1.9.134) — overlay badge stays'); }+ else console.log(`[identity] register failed: ${((r && r.error) || raw || '').slice(0, 140)}`);+ return false;+ }+ _identitySupported = true; _identityRegistered = true;+ console.log('[identity] Adom.Pup registered (HKCU AUMID + Start Menu pin -> managed pup window)');+ return true;+ } catch (e) { console.log(`[identity] register threw: ${e.message}`); return false; }+}+async function stampPupIdentity(sessionId) {+ if (process.platform !== 'win32' || !sessionId || _identitySupported === false) return false;+ if (!_identityRegistered) { await ensurePupIdentityRegistered(); if (_identitySupported === false) return false; }+ try {+ const r = await chrome.adCommand('desktop_set_window_identity', {+ titleContains: `(session: ${sessionId}`, cacheKey: `pup-${sessionId}`,+ appId: PUP_APP_ID, displayName: PUP_DISPLAY_NAME, iconPath: pupIconPath(),+ }, { timeoutMs: 6000 });+ const raw = r && (typeof r.output === 'string' ? r.output : JSON.stringify(r.output || ''));+ if ((r && r.success === false) || _isUnknownVerb(r, raw)) {+ if (_isUnknownVerb(r, raw)) _identitySupported = false;+ return false;+ }+ const o = raw ? JSON.parse(raw) : {};+ const data = o.data || o;+ const ok = !!(data.ok || (data.applied && data.applied.length));+ if (ok) console.log(`[identity] "${sessionId}" stamped: ${(data.applied || []).join(',') || 'ok'}`);+ return ok;+ } catch (e) { return false; }+}+// Brand a pup window: full identity on capable AD, overlay badge fallback on older AD.+async function brandPupWindow(sessionId) {+ const stamped = await stampPupIdentity(sessionId);+ if (stamped) return true;+ return badgePupTaskbar(sessionId);+} async function badgePupTaskbar(sessionId) { if (process.platform !== 'win32') return false; const icon = pupBadgeB64();@@ -2186,7 +2246,7 @@ async function healOffscreenWindow(session, sessionId) { if (!b || (b.left > -1000 && b.top > -1000)) return null; // on-screen — healthy, hands off (v1.8.64: catches the -1310 staging spot too) console.log(`[heal] "${sessionId}" window stranded off-screen (${b.left},${b.top}) — re-parking + re-badging`); const r = await parkSessionWindowDirect(page, sessionId);- badgePupTaskbar(sessionId).catch(() => {});+ brandPupWindow(sessionId).catch(() => {}); return r; } catch (e) { return null; } }@@ -2923,6 +2983,12 @@ async function launchSession(sessionId, url, { freshProfile = false, profile, st console.log(`Welcome page failed to load (non-fatal): ${e.message}`); } + // v1.8.73: stamp the window IDENTITY at CREATE (per the AD contract: identity is per-HWND —+ // stamping only at park would leave the window reading "Google Chrome for Testing" during its+ // whole load). The welcome page just set the "(session: <id>)" title, so AD can resolve it.+ // Fire-and-forget; the park re-brands (with badge fallback) as belt-and-suspenders.+ if (process.platform === 'win32') { 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 // is already placed on-screen (even at z-bottom), the user sees a blip on top of their work@@ -2943,8 +3009,8 @@ async function launchSession(sessionId, url, { freshProfile = false, profile, st console.log(`[park] "${sessionId}": ${r}`); // v1.8.53: badge IMMEDIATELY after the park (the title suffix already exists — // no more blind 1.5s/3.5s waits John noticed) + one safety retry.- badgePupTaskbar(sessionId).catch(() => {});- setTimeout(() => { badgePupTaskbar(sessionId).catch(() => {}); }, 1200);+ brandPupWindow(sessionId).catch(() => {});+ setTimeout(() => { brandPupWindow(sessionId).catch(() => {}); }, 1200); return r; }) .catch(e => (be.browser._bgResult = 'error'));@@ -3894,7 +3960,7 @@ const server = http.createServer(async (req, res) => { // park verdict lands instead of blind 1.5s/3.5s timers. This is only a fallback for // the REUSED-window path (no park runs there, but the badge may have been cleared). if (process.platform === 'win32') {- setTimeout(() => { badgePupTaskbar(sessionId).catch(() => {}); }, 300);+ setTimeout(() => { brandPupWindow(sessionId).catch(() => {}); }, 300); } // Verbose browser report: which browser we actually drove, what else is available. const brep = browserPickReport();@@ -4085,7 +4151,7 @@ const server = http.createServer(async (req, res) => { healOffscreenWindow(session, sessionId).catch(() => {}); const now = Date.now(); if (!session._badgedAt || now - session._badgedAt > 600000) {- badgePupTaskbar(sessionId).then(ok => { if (ok) session._badgedAt = Date.now(); }).catch(() => {});+ brandPupWindow(sessionId).then(ok => { if (ok) session._badgedAt = Date.now(); }).catch(() => {}); } } const info = await describeTab(tab, session.activeTabId);@@ -4145,7 +4211,7 @@ const server = http.createServer(async (req, res) => { const now = Date.now(); if (!session._badgedAt || now - session._badgedAt > 600000) { // v1.8.58: only debounce on SUCCESS — a failed badge retries on the next call.- badgePupTaskbar(sessionId).then(ok => { if (ok) session._badgedAt = Date.now(); }).catch(() => {});+ brandPupWindow(sessionId).then(ok => { if (ok) session._badgedAt = Date.now(); }).catch(() => {}); } } const info = await describeTab(tab, session.activeTabId);@@ -5850,6 +5916,10 @@ server.listen(PORT, BIND_HOST, async () => { // installs/updates it), so the FIRST open is instant. Only fetches CfT if the box // has literally no Chrome/Edge and no cached CfT — otherwise it's a cheap no-op. try { chrome.warmup(); } catch (e) { console.error('warmup failed:', e.message); }+ // v1.8.73: register the Adom.Pup app identity once per bridge run (HKCU AUMID + Start Menu+ // shortcut whose pin launches a managed pup window). Loopback call from the bridge itself —+ // trusted transport, no relay gating. Self-gates on AD <1.9.134 (unknown verb).+ setTimeout(() => { ensurePupIdentityRegistered().catch(() => {}); }, 2500); // Recover sessions from disk (reconnect to Chrome windows that survived bridge restart) try {