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
Document the solved background-by-default/focus-steal playbook in the dev skill + CHANGELOG so it's never re-derived (off-screen launch, z-bottom park, AttachThreadInput focus handback, hidden PowerShell) plus the dead-ends
2 files changed
+84
CHANGELOG.md+21@@ -1,5 +1,26 @@ # Changelog — Adom Desktop Puppeteer Bridge +## 1.8.24 → 1.8.34 — 2026-07-13 — Background-by-default, actually correct (no pop / no cover / no focus steal / no cmd flash)++The long-standing "pup window pops up, covers my work, steals my typing" problem, solved properly and+made deterministic + self-verifying. **The full technique + the dead-ends are documented in the+`pup-bridge-dev` skill ("Background-by-default without stealing focus") — read that before touching window+placement; do NOT re-derive it.** Summary:+- **Launch OFF-SCREEN** (`--window-position=-32000,-32000`, not `--start-maximized`) so it never visually+ pops; CDP still renders it (screenshots work).+- **Park on-screen at z-bottom** sized to the monitor (`SetWindowPos HWND_BOTTOM SWP_NOACTIVATE`) → behind+ the user's windows (invisible) but taskbar-clickable.+- **Hand keyboard focus back with `AttachThreadInput`** (snapshot the user's window pre-launch; attach+ input threads to bypass the foreground lock; `SetForegroundWindow` back to it; re-run over ~2.5s as the+ browser re-grabs during startup). Deterministic `backgrounded` instead of a flaky focus steal.+- **All window-management PowerShell runs HIDDEN** (`execFileSync` + `windowsHide` + `-WindowStyle Hidden`+ via `runPsHidden`), so no cmd/terminal window ever flashes.+- **Self-verifying:** every open re-reads `GetForegroundWindow()` and reports a `background` field+ (`backgrounded`/`still-foreground`). navigate/reload/switch_tab no longer re-raise a background window.+- Dead-ends rejected (all worse): start-maximized+lower (visible pop), bare `SetForegroundWindow` (lock),+ minimize-and-leave (breaks screenshots), `WS_EX_NOACTIVATE` (breaks taskbar-click + Chrome self-activates),+ minimize-loop (flicker), pup-side localhost probe (AD already reprimands localhost on failure).+ ## 1.8.23 — 2026-07-10 — Agent-update signalling (auto-flash), keystroke-safe raise, render_html Field report from an agent that staged a 4-tab task and lost track of which window it was updating.
dev-skills/pup-bridge-dev/SKILL.md+63@@ -220,6 +220,69 @@ What we learned and baked into the code: reading the third saw pre-update text. When auditing against a doc, verify the EXACT file you read; when publishing multi-copy docs, push and verify every copy. +## Background-by-default without stealing focus — THE SOLVED PLAYBOOK (do NOT re-derive this)++This is the single hardest problem in the bridge and it has been solved TWICE from scratch. If you are+touching `browser_open_window`, window placement, focus, or "the pup window popped up / stole my typing /+flashed a terminal", **read this whole section before changing anything.** The final design lives in+`osBackgroundWindowByPid` + `tryDetachedLaunch` + the raise handler (search those).++**The core problem:** headful Chrome/Edge **aggressively grab the OS foreground** — they call+`SetForegroundWindow` on their own window at launch and every time the window is shown. So a naive open+(a) pops the window over the user's work, (b) steals keyboard focus for the ~1–3s of browser startup, so+the user's keystrokes land in the invisible pup window. Requirements that must hold SIMULTANEOUSLY:+R1 never visually pop / cover the user's work · R2 the window is reachable (taskbar-clickable) · R3 CDP+`browser_screenshot` still works · R4 never steal keyboard focus · R5 no console/terminal flash.++**The working design (4 techniques layered — all four are required):**++1. **Launch OFF-SCREEN** → satisfies R1. Background windows launch with+ `--window-position=-32000,-32000` (NOT `--start-maximized`; strip it when `!foreground`). The window is+ created far past any monitor, so nothing ever appears on screen during startup. CDP renders regardless+ of position, so screenshots/eval/record all still work.+2. **Park ON-SCREEN at the bottom of the z-order** → satisfies R2 + R3. After CDP connects, move it back+ on-screen sized to the monitor at the very back:+ `SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, GetSystemMetrics(0), GetSystemMetrics(1), SWP_NOACTIVATE)`.+ Fully behind the user's windows (invisible) but it has a real taskbar button to click. (Leaving it+ off-screen breaks R2 — the taskbar button "restores" it off-screen where you can't see it.)+3. **Hand keyboard focus back via `AttachThreadInput`** → satisfies R4, and is the crux. Plain+ `SetForegroundWindow(usersWindow)` is BLOCKED by Windows' foreground lock. The workaround:+ - snapshot the user's window with `GetForegroundWindow()` **before** spawning the browser;+ - after the browser grabs focus: `AttachThreadInput(pupWindowThreadId, myThreadId, true)` (get the pup+ window's thread via `GetWindowThreadProcessId`), which shares the foreground privilege → then+ `SetForegroundWindow(usersWindow)` + `BringWindowToTop(usersWindow)` succeeds → detach with+ `AttachThreadInput(..., false)`.+ - Re-run the whole background+handback a few times over ~2.5s (600/1400/2500ms) because the browser+ re-grabs during startup. The initial `priorForeground` is stashed on `browser._priorForeground` so+ the after-sizing re-background can restore too.+4. **Run all this PowerShell HIDDEN** → satisfies R5. Use `execFileSync('powershell.exe', [...args],+ {windowsHide:true})` + `-WindowStyle Hidden` (see `runPsHidden`/`runPsHiddenAsync`), NEVER+ `execSync('powershell …')` — the latter goes through cmd.exe and flashes a console window. (A cmd blip+ during a redeploy is different: that's AD respawning the node process, not our runtime.)++**SELF-VERIFY, always.** The PS re-reads `GetForegroundWindow()` after the handback and returns+`backgrounded` | `still-foreground` | `no-window`; that surfaces as the `background` field in the+open response + `getSessionInfo`. Test on a box with a LIVE interactive desktop (an RDP-disconnected box+reports a degenerate/blank foreground and lies "backgrounded"). ADOMGPU with Fusion open is a good rig.++**`browser_raise_os_window` is the inverse:** move the window on-screen + maximize via CDP+`Browser.setWindowBounds`, then foreground it. If you ever add a non-activatable style (see below), raise+must clear it first.++**Dead ends — DO NOT reintroduce these (each was tried and is worse):**+- **`--start-maximized` + send-to-back after connect** → the window is foreground/maximized for 1–3s+ before you lower it = the visible pop. This is the naive version; off-screen launch replaces it.+- **`SetForegroundWindow(usersWindow)` alone** → silently no-ops (foreground lock). You MUST use the+ `AttachThreadInput` attach/detach dance.+- **Minimize-and-leave (`SW_MINIMIZE`, don't re-show)** → drops focus cleanly BUT a minimized window stops+ compositing, so `Page.captureScreenshot` **times out** (breaks R3). Rejected.+- **`WS_EX_NOACTIVATE` on the window** → Chrome self-activates anyway (so it doesn't fix R4), AND a+ non-activatable window can't be brought up by a taskbar click (breaks R2). Rejected.+- **Minimize→re-show loop as the focus drop** → on an on-screen window the minimize/restore is a visible+ flicker; a burst of retries strobes. The `AttachThreadInput` handback needs no minimize, so no flicker.+- **A pup-side localhost reachability probe** → redundant; AD core already reprimands a localhost URL when+ it fails to load (and HD's working localhost passes clean). Don't re-add it.+ ## Publishing (the wiki has TWO storage layers — don't mix them) Full recipe in `PUBLISHING.md`. The load-bearing rules: