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
Three fixes from John's live testing. (1) The view-toggle resize, finally traced to the true cause: osRaiseSessionWindow, the choke point every raise goes through, unconditionally rewrote the window's normal rect to 1280x800 and re-maximized on EVERY raise, so even an already-maximized window visibly snapped small-then-big on each user-click view toggle. It now reads the current bounds first: already-maximized windows are not touched at all, on-screen non-maximized windows get maximize only, and only a genuinely off-screen window gets the full rect-rewrite rescue. (2) The public view toggle never falls back to the jar relaunch anymore: the header probe raced late-painting headers and wrongly concluded the server ignored the param, destroying the user's window; the probe now only logs and the param is trusted. (3) Annotate's pen and arrow cursors looked like the old crosshair build on CSP-strict sites (Hacker News blocks data: images, which CSS cursor urls load under img-src): the cursor is now an inline-SVG sprite following the pointer with the native cursor hidden, immune to any site's CSP, hotspots preserved.
4 files changed
+69−24
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "2.0.133",- "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.133/adom-bridge-puppeteer-v2.0.133.zip",- "sha256": "7bfd2814068dd1e952a1590e09c803e5ed1397defb85adbbdf9882650477294b",- "size": 887245,+ "version": "2.0.134",+ "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.134/adom-bridge-puppeteer-v2.0.134.zip",+ "sha256": "1a62e26143c761a18a9bcb979debdef5782d7bbc9a531d5513baf92c539baa38",+ "size": 888411, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-2.0.133+2.0.134
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "2.0.133",+ "version": "2.0.134", "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+63−18@@ -5676,24 +5676,47 @@ const ANNOTATE_OVERLAY_JS = `window.__pupStartAnnotate = function (initTool) { function tipFor(el, text) { el.addEventListener('mouseenter', function () { tipShow(el, text); }); el.addEventListener('mouseleave', tipHide); el.addEventListener('pointerdown', tipHide); el.removeAttribute('title'); } tipFor(grip, 'Drag to move this toolbar anywhere it is not in your way.'); var ctx = cv.getContext('2d');- // ── Photoshop-style tool cursors: SVG data-URI, hotspot at the working tip, pen carries a- // live swatch of the current color (rebuilt on color change).- function penCur() {- var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">'+ // ── Photoshop-style tool cursors (v2.0.134, John's HN report: "pen mode shows the crosshair,+ // must be old code"): it was NOT old code. The pen/arrow cursors were CSS+ // url(data:image/svg+xml) images, and CSS cursor images load under the PAGE'S CSP img-src —+ // sites that don't allow data: (Hacker News: img-src 'self' ...) silently block the image and+ // CSS falls back to the declared crosshair, so one build looked new on the wiki and old on HN.+ // Fix: never FETCH a cursor at all. Hide the native cursor over the canvas and follow the+ // pointer with an inline-SVG sprite — DOM markup, which no CSP gates — hotspot preserved+ // (pen tip at 5,19; arrow tip at 2,2). Pen still carries the live color swatch.+ var spr = document.createElement('div');+ spr.style.cssText = 'position:fixed;left:0;top:0;z-index:2147483647;pointer-events:none;display:none;will-change:transform;';+ wrap.appendChild(spr);+ var sprHot = [0, 0];+ function penSvg() {+ return '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">' + '<path d="M5 19 L6.5 14.5 L16 5 a2 2 0 0 1 2.8 2.8 L9.5 17.5 Z" fill="#e5e7eb" stroke="#111" stroke-width="1.2"/>' + '<path d="M5 19 L6.5 14.5 L9.5 17.5 Z" fill="' + S.color + '" stroke="#111" stroke-width="0.8"/>' + '<circle cx="19.5" cy="19.5" r="3.5" fill="' + S.color + '" stroke="#fff" stroke-width="1"/></svg>';- return 'url("data:image/svg+xml,' + encodeURIComponent(svg) + '") 5 19, crosshair'; }- function arrowCur() {- var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">'+ function arrowSvg() {+ return '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">' + '<path d="M2 2 L11 20 L13.5 13.5 L20 11 Z" fill="' + S.color + '" stroke="#fff" stroke-width="1.4"/></svg>';- return 'url("data:image/svg+xml,' + encodeURIComponent(svg) + '") 2 2, crosshair'; } function setCursor() {- cv.style.cursor = S.tool === 'pen' ? penCur() : S.tool === 'arrow' ? arrowCur()- : S.tool === 'text' ? 'text' : 'crosshair';+ if (S.tool === 'pen' || S.tool === 'arrow') {+ cv.style.cursor = 'none';+ spr.innerHTML = S.tool === 'pen' ? penSvg() : arrowSvg();+ sprHot = S.tool === 'pen' ? [5, 19] : [2, 2];+ // shown on the next pointer move, so it never appears at a stale position+ } else {+ cv.style.cursor = S.tool === 'text' ? 'text' : 'crosshair';+ spr.style.display = 'none';+ }+ }+ function sprTrack(e) {+ if (S.tool !== 'pen' && S.tool !== 'arrow') return;+ spr.style.display = 'block';+ spr.style.transform = 'translate(' + (e.clientX - sprHot[0]) + 'px,' + (e.clientY - sprHot[1]) + 'px)'; }+ cv.addEventListener('pointermove', sprTrack);+ cv.addEventListener('pointerenter', sprTrack);+ cv.addEventListener('pointerleave', function () { spr.style.display = 'none'; }); function fit() { var d = window.devicePixelRatio || 1; cv.width = innerWidth * d; cv.height = innerHeight * d; ctx.setTransform(d, 0, 0, d, 0, 0); redraw(); } function drawStroke(st) { ctx.strokeStyle = st.color; ctx.fillStyle = st.color; ctx.lineWidth = 3.5; ctx.lineCap = 'round'; ctx.lineJoin = 'round';@@ -6294,8 +6317,25 @@ async function osRaiseSessionWindow(session, sessionId, reason) { try { const cdp = await session.page.target().createCDPSession(); const { windowId } = await cdp.send('Browser.getWindowForTarget');- await cdp.send('Browser.setWindowBounds', { windowId, bounds: { left: 0, top: 0, width: 1280, height: 800, windowState: 'normal' } });- await cdp.send('Browser.setWindowBounds', { windowId, bounds: { windowState: 'maximized' } });+ // v2.0.134 (John: "you're STILL resizing the browser window on me" — traced HERE, not to+ // the relaunch): this used to UNCONDITIONALLY rewrite the normal rect to 1280x800@0,0 and+ // re-maximize on EVERY raise, so even an already-maximized, on-screen window visibly+ // snapped small-then-big each time a view toggle (or any user-click raise) went through.+ // The rewrite exists only to rescue a window stranded at the -32000 birth park, where the+ // normal rect must land on-screen before maximize means anything. So: read the CURRENT+ // bounds first — already-maximized windows are NOT touched at all, on-screen non-maximized+ // windows get maximize only, and only a genuinely off-screen window gets the full+ // rect-rewrite + maximize rescue.+ const _cur = await cdp.send('Browser.getWindowBounds', { windowId }).catch(() => null);+ const _b = _cur && _cur.bounds;+ const _off = _b && ((_b.left != null && _b.left <= -10000) || (_b.top != null && _b.top <= -10000));+ if (_off) {+ await cdp.send('Browser.setWindowBounds', { windowId, bounds: { left: 0, top: 0, width: 1280, height: 800, windowState: 'normal' } });+ await cdp.send('Browser.setWindowBounds', { windowId, bounds: { windowState: 'maximized' } });+ } else if (!_b || (_b.windowState !== 'maximized' && _b.windowState !== 'fullscreen')) {+ await cdp.send('Browser.setWindowBounds', { windowId, bounds: { windowState: 'maximized' } });+ }+ // already maximized (or fullscreen): leave the geometry completely alone — raise only. } catch (e) {} try { await session.page.evaluate(() => { const a = document.activeElement; if (a && a.blur) a.blur(); }); } catch (e) {} try { await session.page.bringToFront(); } catch (e) {}@@ -13136,13 +13176,18 @@ const server = http.createServer(async (req, res) => { } catch (e) {} let _a = null; if (view === 'authed') { try { _a = await checkWikiAuth(_at.page); } catch (e) {} }- const _renderedAuthed = view === 'authed' ? !!(_a && _a.authed) : _renderLoggedOut === false;+ const _renderedAuthed = view === 'authed' ? !!(_a && _a.authed) : false;+ // v2.0.134 (John: "you're STILL resizing the window on me"): the public direction+ // NEVER falls back to the jar relaunch anymore. The old guard probed the header for+ // the Login button right after domcontentloaded — when the header rendered a beat+ // late the probe missed, wrongly concluded "server ignored the param", and+ // relaunched the window (the resize John kept seeing). The param is live on prod+ // and verified; a possibly-late header paint is never worth destroying the user's+ // window over. The probe survives for LOGGING only. if (view === 'public' && _renderLoggedOut === false) {- // Old server: the param was ignored. Strip it back off and fall through to the- // legacy isolated-jar relaunch below.- console.log(`[wikiview] "${sessionId}" server ignored ?view=public (old wiki build?) — restoring URLs and falling back to the jar relaunch`);- for (const t of _wTabs) { let tu = null; try { tu = t.page.url(); } catch (e) {} if (tu) { try { await t.page.goto(wikiStripPublicParam(tu), { waitUntil: 'domcontentloaded', timeout: 25000 }); } catch (e) {} } }- } else if (view === 'authed' && !_renderedAuthed) {+ console.log(`[wikiview] "${sessionId}" header probe did not see the logged-out marker yet (late paint or old server) — TRUSTING ?view=public, staying in place (no relaunch, by design)`);+ }+ if (view === 'authed' && !_renderedAuthed) { // Param stripped but the jar holds no live wiki session — continue into the // existing mint / auto-sign / manual-login machinery below with a clean URL. console.log(`[wikiview] "${sessionId}" param strip left the window logged out (no live session) — continuing to the sign-in path`);