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
Jump-list blip: stop the wiki auth-state churn that repeatedly destroyed and recreated the taskbar button. Every auth flip re-stamps the window identity, which mints a new AUMID, which destroys+recreates the taskbar button (documented Win11 behavior) — and a button destroyed under an open right-click flyout is exactly the reported 'menu shows ~300ms then disappears, second click works' bug. Two causes fixed: (1) checkWikiAuth collapsed a real logged-out answer, a non-ok response, and a THROWN probe (page mid-navigation, frame detached) all into authed:false, so the state flip-flopped authed->transient-failure->authed and re-stamped on every bounce; it now reports unknown:true when it could not determine, and every caller forwards that so an indeterminate probe changes nothing. (2) setWikiAuthedState now applies a 15s flip cooldown: the state is still recorded but the expensive, button-destroying re-stamp is suppressed, so rapid churn can never recreate the button repeatedly.
4 files changed
+45−14
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.9.92",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.92/adom-bridge-puppeteer-v1.9.92.zip",- "sha256": "edc5dc4c6a8dd439cd0803fc78d44205727c1a679bc0aae39b73905e16668ef7",- "size": 366514,+ "version": "1.9.93",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.93/adom-bridge-puppeteer-v1.9.93.zip",+ "sha256": "10226a5310997d022261358bf5e28edcedcef9a1dcbd4dc7291b501d659e8128",+ "size": 367447, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.92+1.9.93
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.9.92",+ "version": "1.9.93", "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+39−8@@ -2312,12 +2312,27 @@ async function unregisterPupAumid(appId) { // v1.9.12: record the wiki auth state and RE-BRAND if it changed, so the taskbar icon flips // between the browser-window glyph and the solid padlock the moment login state is known. // Returns true if the state changed (i.e. a re-stamp was issued).-async function setWikiAuthedState(session, sessionId, authed) {+async function setWikiAuthedState(session, sessionId, authed, opts = {}) { if (!session) return false;+ // v1.9.93 — an INDETERMINATE probe is not a state change. Callers pass the whole checkWikiAuth+ // result; if it could not determine the answer, keep the last known state and re-stamp NOTHING.+ if (opts.unknown) return false; const next = !!authed; if (session._wikiAuthed === next) return false;+ // v1.9.93 — FLIP COOLDOWN. Every flip re-stamps the identity, which mints a new AUMID and DESTROYS+ // + recreates the taskbar button; doing that repeatedly is what made the right-click jump list+ // "blip out" (the flyout dies with the button under it). A genuine login/logout happens once and is+ // worth one flip; a rapid re-flip is churn, so absorb it. The state is still recorded — only the+ // expensive, user-visible re-stamp is suppressed — so the icon settles on the next legitimate brand.+ const FLIP_COOLDOWN_MS = 15000;+ const sinceFlip = Date.now() - (session._wikiAuthFlipAt || 0); session._wikiAuthed = next; if (process.platform !== 'win32') return true;+ if (session._wikiAuthFlipAt && sinceFlip < FLIP_COOLDOWN_MS) {+ console.log(`[identity] "${sessionId}" wiki login state -> ${next ? 'LOGGED IN' : 'logged out'} but SUPPRESSING the re-stamp (${Math.round(sinceFlip / 1000)}s since the last flip; an AUMID flip destroys the taskbar button and would kill an open jump list)`);+ return true;+ }+ session._wikiAuthFlipAt = Date.now(); console.log(`[identity] "${sessionId}" wiki login state -> ${next ? 'LOGGED IN (solid green glyph)' : 'logged out (hollow glyph)'}`); // v1.9.15: RETRY the re-stamp. After a view toggle the window is BRAND NEW and AD resolves it by // its "(session: <id>)" title suffix, which the title injector adds only once the relaunched page@@ -2745,13 +2760,26 @@ function isWikiUrl(u) { try { return /(^|\.)wiki\.adom\.inc$/i.test(new URL(u).h // digikey.com, even adom.cloud proxies) get NOTHING — no auth routing, hints, or glyph. function isAdomUrl(u) { try { const h = new URL(u).hostname; return h === 'adom.inc' || /\.adom\.inc$/i.test(h); } catch (e) { return false; } } // Ask the page (using ITS cookies) whether the wiki session is authenticated.+// v1.9.93 (John: "the jump list still blips out") — an INDETERMINATE auth probe must never be+// reported as "logged out". The old version collapsed three different outcomes into {authed:false}:+// a real logged-out answer, a non-ok HTTP response, and a THROWN probe (page mid-navigation, frame+// detached, offline). That made the auth state FLIP-FLOP (authed -> transient failure reads as+// logged-out -> authed again), and each flip re-stamps the window identity, which mints a NEW AUMID,+// which DESTROYS AND RECREATES the taskbar button (documented Win11 behavior). A button destroyed+// under an open right-click flyout is exactly the "menu shows ~300ms then disappears, second click+// works" bug. So: report `unknown:true` when we could not determine, and let callers ignore it. async function checkWikiAuth(page) { try { return await page.evaluate(async () => {- try { const r = await fetch('/auth/me', { credentials: 'same-origin' }); if (!r.ok) return { authed: false }; const j = await r.json(); return { authed: !!(j && j.user), user: j && j.user && (j.user.displayName || j.user.username) }; }- catch (e) { return { authed: false }; }+ try {+ const r = await fetch('/auth/me', { credentials: 'same-origin' });+ if (!r.ok) return (r.status === 401 || r.status === 403) ? { authed: false } : { authed: false, unknown: true };+ const j = await r.json();+ return { authed: !!(j && j.user), user: j && j.user && (j.user.displayName || j.user.username) };+ }+ catch (e) { return { authed: false, unknown: true }; } // probe failed — NOT evidence of logout });- } catch (e) { return { authed: false }; }+ } catch (e) { return { authed: false, unknown: true }; } // evaluate threw — NOT evidence of logout } // v1.9.88 — AUTO-SIGN the wiki from AD's stored identity, so pup NEVER makes the user hand-type a // login into its no-memory browser. John, verbatim: "the ai calling you knows exactly who i am and@@ -6535,7 +6563,7 @@ const server = http.createServer(async (req, res) => { await session.page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 25000 }); session._wikiView = view; const a3 = await checkWikiAuth(session.page).catch(() => ({ authed: false }));- await setWikiAuthedState(session, sessionId, !!(a3 && a3.authed));+ await setWikiAuthedState(session, sessionId, !!(a3 && a3.authed), { unknown: !!(a3 && a3.unknown) }); try { await setWikiModeGlyph(session.page, (a3 && a3.authed) ? '● Logged in' : '○ Public'); } catch (e) {} session._wikiJumplistKey = undefined; // the toggle's own label flips updateWikiJumplist(session, sessionId).catch(() => {});@@ -6667,7 +6695,7 @@ const server = http.createServer(async (req, res) => { let a = null; try { a = await checkWikiAuth(s2.page); } catch (e) {} try { await setWikiModeGlyph(s2.page, (a && a.authed) ? '\u25CF Logged in' : '\u25CB Public'); } catch (e) {}- try { await setWikiAuthedState(s2, sessionId, a && a.authed); } catch (e) {}+ try { await setWikiAuthedState(s2, sessionId, a && a.authed, { unknown: !!(a && a.unknown) }); } catch (e) {} // v1.9.9: result caption — names WHO you're signed in as (John's ask), or says the // one-time sign-in is still needed. Replaces the "Switching…" caption (same id). const _doneMsg = a && a.authed@@ -8032,8 +8060,11 @@ const server = http.createServer(async (req, res) => { setTimeout(async () => { try { const a = await checkWikiAuth(session.page);- await setWikiModeGlyph(session.page, a && a.authed ? '\u25CF Logged in' : '\u25CB Public');- await setWikiAuthedState(session, resolvedId, a && a.authed);+ // v1.9.93: an indeterminate probe must not repaint the glyph OR flip the identity.+ if (!(a && a.unknown)) {+ await setWikiModeGlyph(session.page, a && a.authed ? '\u25CF Logged in' : '\u25CB Public');+ await setWikiAuthedState(session, resolvedId, a && a.authed, { unknown: false });+ } } catch (e) {} }, 1800); }