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
v2.0.290: Foreground caption now shows FIRST then raises (awaited, was fire-and-forget so it trailed the window ~5s), and names the initiating AI thread + reason (John). Applies to pup_raise_os_window and the osRaiseSessionWindow choke point.
4 files changed
+23−14
adom-bridge-puppeteer-manifest.json+5−5@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "2.0.289",- "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.289/adom-bridge-puppeteer-v2.0.289.zip",- "sha256": "f62aa77cd9ee41deba30818d5b928dd06902df8d666cc828a0a9dea9ea8442bd",- "size": 971202,+ "version": "2.0.290",+ "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.290/adom-bridge-puppeteer-v2.0.290.zip",+ "sha256": "6120459519f76aedf3b6327e348462057134fc7daa8c908920a444816a2407af",+ "size": 971524, "verbPrefixes": [ "browser_", "desktop_recorder_",@@ -12,5 +12,5 @@ ], "healthEndpoint": "/status", "statusVerb": "browser_readiness",- "released_at": "2026-08-22T23:04:11.887313Z"+ "released_at": "2026-08-23T10:54:16.411958Z" }
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-2.0.289+2.0.290
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "2.0.289",+ "version": "2.0.290", "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+16−7@@ -5790,14 +5790,19 @@ const _fgToastAt = new Map(); // only visible if you went looking for it. Now the reason appears ON SCREEN the moment the window // arrives, so the justification is accountable in real time. Deliberately NOT gated by minimalTouch: // this is a user-protection signal, not cosmetic machinery.-async function captionForegroundReason(sessionId, reason, verb) {+async function captionForegroundReason(sessionId, reason, verb, thread) { // v1.9.259: user prefs — toggle + duration slider (500..5000ms, default 2000). if (pupSettings().fgCaption !== 'on') return; const _capMs = Math.max(500, Math.min(5000, Number(pupSettings().fgCaptionMs) || 2000)); try { const why = String(reason || '').trim(); if (!why) return;- await pupCaption(`Adom brought "${sessionId}" to the front. Reason: "${why}"`, _capMs, 'pup-fg-reason', _capMs);+ // v2.0.290 (John: "if you foreground, the caption must say WHICH ai-thread is doing it, and the+ // reason"). Name the initiating thread (the caller), falling back to the window's owner thread.+ const _s = sessions.get(sessionId);+ const _th = (thread && String(thread).trim()) || (_s && (_s.owner || _s._ownerThread)) || null;+ const who = _th ? `AI thread "${_th}"` : 'Adom';+ await pupCaption(`${who} brought "${sessionId}" to the front. Reason: "${why}"`, _capMs, 'pup-fg-reason', _capMs); } catch (e) {} } @@ -7724,7 +7729,7 @@ async function wikiCaption(text, expiresInMs) { // at, which is why John "never saw the switch work." CDP place+maximize → blur → bringToFront → // clear WS_EX_NOACTIVATE + SetForegroundWindow (via the detached/browser PID, with a WMI child // fallback for launcher→browser PID relaunches). Best-effort throughout; never throws.-async function osRaiseSessionWindow(session, sessionId, reason) {+async function osRaiseSessionWindow(session, sessionId, reason, thread) { // v2.0.136 (John: "why are you showing this caption when i click annotate in the chrome bar?"): // if this window is ALREADY the user's foreground window, there is nothing to bring to the // front — the user is literally looking at it (they just clicked a control inside it, e.g. the@@ -7749,7 +7754,10 @@ async function osRaiseSessionWindow(session, sessionId, reason) { // OTHER raise paths routed here and captioned NOTHING. Enforce at the choke point: every raise // through this function captions its reason for 2s. Callers that already narrate (assisted_click) // pass reason=null to avoid a double caption.- try { if (reason) captionForegroundReason(sessionId, reason, 'osRaiseSessionWindow').catch(() => {}); } catch (e) {}+ // v2.0.290 (John: "you showed the caption ~5s LATER, which defeats the point — always show the+ // caption FIRST, then foreground"). AWAIT the caption so it is on screen BEFORE the raise runs;+ // fire-and-forget let the fast raise win the race and the caption trailed the window.+ try { if (reason) await captionForegroundReason(sessionId, reason, 'osRaiseSessionWindow', thread); } catch (e) {} try { try { const cdp = await session.page.target().createCDPSession();@@ -16074,9 +16082,10 @@ const server = http.createServer(async (req, res) => { // three of John's foregrounded windows lost their overlay. Re-assert it after the raise. setTimeout(() => { updateTabCountBadge(session, args.sessionId || session._sessionId).catch(() => {}); }, 1200); try { recordAgentUpdate(session, command, { updateNote: `FOREGROUND: ${fgReason}` }, session.activeTabId); } catch (e) {}- // v1.9.201: show the caller's justification on screen for ~1s, so a foreground is never- // silent and John can see WHY it happened as it happens.- captionForegroundReason(sessionId, fgReason, command).catch(() => {});+ // v1.9.201 / v2.0.290: show the caller's justification (and WHICH ai-thread) on screen+ // BEFORE the window comes forward, so the caption always precedes the reveal — never trails+ // it. AWAITED (fire-and-forget let the raise win the race, John saw the caption ~5s late).+ try { await captionForegroundReason(sessionId, fgReason, command, _callerThread); } catch (e) {} try { // v1.9.149 (John's audit: two of three raised windows came up 0,0 full-screen, not the // standard frame): the reveal must present the SAME work-area-minus-10px geometry the