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
NEW PLACEMENT RULES (John, 2026-08-20): windows are born on-screen at their final rect, background is a preference not a precondition, a taskbar click always works, identity is captured at birth, taskbar branding is async and after. Single source of truth in pup-bridge-dev R1-R5; the other skills now defer to it instead of contradicting it.
3 files changed
+90−69
dev-skills/pup-bridge-dev/SKILL.md+60−62@@ -340,74 +340,72 @@ the user's keystrokes land in the invisible pup window. Requirements that must h R1 never visually pop / cover the user's work · R2 the window is reachable (taskbar-clickable) · R3 CDP `pup_screenshot` still works · R4 never steal keyboard focus · R5 no console/terminal flash. -**The working design (v1.8.51+). The KEY insight, found after a week of warring with re-park loops:-`--no-startup-window` eliminates the focus steal at the SOURCE, so there is NO war to fight.**+**THE RULES (John, 2026-08-20). These SUPERSEDE every earlier placement text in this repo.** They+were set after a pup window opened off-screen behind a live taskbar button for the second time in one+day: *"you need to allow a user to open a pup window immediately if they want to."* -Windows only grants a process the right to take the foreground AT LAUNCH. With `--no-startup-window`-the browser process starts with NO window at all; pup creates the window via CDP (`newPage()`) moments-later, by which time the browser is a background process and **the OS itself denies it the foreground**.-No steal is possible → no watchdog, no timers, no re-park passes exist → **a user's taskbar click at ANY-moment (200ms in or 2 minutes in) has nothing running against it and always sticks.** Do NOT add any-loop that re-parks after the initial park — every such loop, no matter how clever (input-timing, short-windows, stability watches), eventually yanks a real user's click. John proved each variant failed.+**R1 BIRTH IT ON SCREEN, IN ONE CALL.** `Target.createTarget` accepts `left/top/width/height`, so the+window is CREATED at its final geometry: the PRIMARY MONITOR's work area inset **10px on all four+sides**. There is no staging position, no migration, no "place it once we are sure". The window is+reachable from its first millisecond because it was never anywhere else. -**THE FINAL ALGORITHM (v1.8.55, John-verified "those seemed to open nicely") — DIRECT APIs, one-tiny Win32 call, no PS window-hunting, no timers, no loops.** Lives in `tryDetachedLaunch` (flag) +-`parkSessionWindowDirect` + `osBottomUnlessForeground`:+**R2 BACKGROUND IS THE PREFERENCE, NOT A PRECONDITION.** `--no-startup-window` is what actually keeps+the foreground (the browser process holds no foreground rights by the time CDP creates the window), and+it works with an on-screen rect exactly as well as an off-screen one. If it fails anyway and the window+comes up FOREGROUND: **let the flaw exist.** Log it, move on. Do not fight it, do not move the window,+do not re-park it. John's words: *"just allow that flaw to exist."* Fighting this is what produced every+re-park loop, and every one of those eventually yanked a real click. -1. Spawn with `--no-startup-window --window-position=-32000,-32000` (no `--start-maximized`).- `tryDetachedLaunch` only sets `_needsPark` — there is NO window yet to manage.-2. Connect CDP; in launchSession `newPage()` creates the window (off-screen, unfocused — the OS- denied it the foreground). **The park runs right here — the `await` on newPage IS the- "window exists" event. No wait loop, no polling.**-3. `parkSessionWindowDirect(page, sessionId)`:- - hwnd via **AD `desktop_find_window`** (native, matches the "(session: <id>" title suffix the- welcome page just set), retried together with the bottoming below (~8 × 300ms).- - **Z-bottom UNCONDITIONALLY via AD-core** (v1.8.63): ONE call —- `desktop_set_window_state {titleContains, state:'bottom', force:true}` (AD ≥1.9.115; both- verbs shipped from pup's feature requests). `force:true` bottoms even a foreground window.- There is deliberately **NO user-click escape hatch at park time** — see the dead ends:- "foreground at park" has non-user causes and honoring it forced never-clicked windows onto- John's screen. A legacy PS fallback (`osBottomUnlessForeground`) remains ONLY for desktops on- AD ≤1.9.114 (no state:'bottom', or force ignored + skip-on-foreground) — delete it once the- fleet is ≥1.9.115.- - **HARD RULE: never move the window on-screen until 'bottomed' is CONFIRMED.** If bottoming- can't be confirmed after the retries, return an honest `park-failed` and leave the window- invisible off-screen — the self-heal (reattach / open_tab / switch_tab / any status query)- retries it. An un-bottomed window moved on-screen lands ON TOP of the user's work (the- nxp.com pop, v1.8.59).- - **Geometry via CDP plain bounds ONLY** (`Browser.setWindowBounds {left,top,width,height}`,- normal state), addressed by windowId — parking the WRONG window is structurally impossible.- Size comes from the page's own `screen.availWidth/Height` WITH RETRIES (a single probe can- fail mid-navigation; retry, don't fall back). NEVER `windowState:'maximized'` and NEVER AD- `desktop_set_window_bounds` — BOTH RAISE the window (see dead ends).- - **TRUST NOTHING — verify the Z-ORDER after placement** (v1.8.66): re-bottom after the move,- then confirm via AD `desktop_list_windows` that pup's hwnd is NOT topmost and sits below at- least one non-pup window. Unconfirmable after a retry → move the window BACK OFF-SCREEN and- report `park-failed`. A window that might be covering the user never stays on-screen. This- check is what caught ten versions of placement bugs the focus-based checks all missed.- - **Self-verify POSITION** via CDP `getWindowBounds` (left/top > -5000), NOT focus — a- focus-only verify passed while the window sat off-screen ("backgrounded" lie).- - **The launch-gap contract:** a user who clicks the taskbar button within the ~2s before the- park lands may see nothing (window still off-screen) or get parked once — they click ONCE- more and the window comes up and STAYS (nothing re-parks after the park exits). That is the- accepted trade; do not "improve" it with foreground detection.-4. The open response awaits the verdict (`_bgPromise`); badge (`badgePupTaskbar`) + flash fire the- INSTANT the park lands — no blind 1.5s/3.5s badge timers.-5. Nothing ever re-parks. `foreground:true` opens skip all of this and launch maximized on-screen.-6. Remaining AD gap (issue filed): a `send-to-back` window state; when AD ships it, delete- `osBottomUnlessForeground` and pup is 100% PS-free.+**R3 A TASKBAR CLICK MUST WORK, ALWAYS, WITH NO HELP FROM PUP.** The moment the user clicks the button,+the window comes to the foreground and they see it. This is a REQUIREMENT, not a trade. Any state that+breaks it — off-screen, hidden, cloaked, `WS_EX_NOACTIVATE` — is forbidden as a resting state and as a+transient one. There is no "launch-gap contract" any more; the old text promised ~2s and the shipped+code could hold a window unreachable for **42 seconds** (8 bottom attempts x a 5s ab timeout + sleeps). -**Supporting principles (subordinate to the algorithm above — where older text conflicts, the-algorithm wins):**+**R4 IDENTITY IS CAPTURED AT BIRTH, EXACTLY, AND NEVER LOST.** The hwnd is resolved in the same locked+moment the window is created and stashed on the page. See `pup-window-targeting`; the mechanism is a+set-diff under `withBirthLock` filtered to this profile's Chrome PIDs, which is exact WITHOUT depending+on a unique birth coordinate (that dependency is what forced the off-screen birth in the first place). -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. **The resting state is ON-SCREEN at the bottom of the z-order** → satisfies R2 + R3.+**R5 THE TASKBAR IDENTITY IS ASYNC AND COMES AFTER.** AUMID stamping, tile art, jump lists, overlay+badges and the button nudge are ALL slower than R1 and must never gate it. The window is on screen and+clickable long before it is correctly branded, and that is the intended order. A user who clicks an+unbranded button still gets their window.++### What this replaced, and why the old text was dangerous++The v1.8.55 playbook had it backwards: it made placement CONDITIONAL on proving z-bottom+(*"never move the window on-screen until 'bottomed' is CONFIRMED"*, *"Unconfirmable → move the window+BACK OFF-SCREEN"*). It listed **R2 the window is reachable (taskbar-clickable)** as a requirement that+must hold simultaneously with the others, then traded it away one paragraph later without saying so.+And the proof it waited for fed its own failure: `zConfirmedBelow` looks the hwnd up in ab's window+enumeration and returns false when absent, while an off-screen window may not be enumerated at all.+Once a window was parked off-screen it could never confirm its way back out.++It also contradicted `pup-user-foreground` rule 1 (*"never off-screen"*, v1.9.133/134), which John set+AFTER his "click the flash, see nothing" bug specifically to reverse this. Two skills disagreed, the+code followed the older one, and either could be cited to justify whatever the code did. That is why+this section is now the single source for placement and the others defer to it.++**Still true, still load-bearing (do not re-derive):**+- `--no-startup-window` is the focus fix, at the source. Keep it.+- Geometry via **CDP bounds only**, addressed by windowId. NEVER `windowState:'maximized'`, NEVER ab+ `desktop_set_window_bounds` — both RAISE the window.+- **Nothing re-parks a window the user has raised.** `userIsUsingWindow()` gates every demotion.+- Bottoming still happens, but only as a best-effort ASSERTION on an already-correctly-placed window.+ It may never move, hide, or off-screen a window as a consequence of failing.+- `foreground:true` opens skip the background dance entirely and come up on-screen, as before.+++**Supporting principles (subordinate to THE RULES above — where older text conflicts, the rules win):**++1. **~~Launch OFF-SCREEN~~ — STRUCK 2026-08-20.** This was the root of every "I clicked the taskbar+ button and nothing came up" report. Windows are born at their FINAL on-screen rect (R1). The+ `--window-position=-32000,-32000` launch arg is also gone (removed in v1.9.135 for a separate+ reason: it poisoned the restore rect of every Chrome-created window, including popups).+2. **The resting state is ON-SCREEN at the bottom of the z-order** → and now the BIRTH state too. On-screen-occluded (behind the user's windows, taskbar-clickable, still compositing) is the ONLY- resting state that keeps screenshots working — see the minimize dead-end.+ state that satisfies R3 while keeping screenshots working — see the minimize dead-end. 3. **DIRECT APIs, not PowerShell** (John: "why are you using powershell? that's heavy. why aren't you just calling direct api's?"). Geometry = CDP `Browser.setWindowBounds` by windowId (exact window, ~10ms). Window lookup = AD `desktop_find_window`. Foregrounding = AD `desktop_bring_to_front`.
dev-skills/pup-user-foreground/SKILL.md+16−4@@ -32,9 +32,17 @@ loaded gun pointed at the user's click. ## The doctrine (current, settled) -1. **Background means BEHIND, never hidden.** Park = on-screen, full work-area size, z-BOTTOM- (never off-screen, never minimized). A taskbar click then reveals a correctly-placed window- with no help from pup (v1.9.133/134, after the "click the flash, see nothing" bug).+1. **Background means BEHIND, never hidden — and now, never ANYWHERE ELSE FIRST.** As of+ 2026-08-20 this is no longer just the resting state, it is the BIRTH state: a window is created+ at its final on-screen rect (primary work area, 10px inset) in the `Target.createTarget` call+ itself, at z-bottom, never off-screen and never minimized. A taskbar click reveals it with no+ help from pup, from the first millisecond of its life.+ You were right in v1.9.133/134 (after the "click the flash, see nothing" bug) and this rule was+ correct the whole time; what it lacked was force over the older `pup-bridge-dev` playbook, which+ said the opposite ("leave the window invisible off-screen") and which the CODE was following.+ **THE PLACEMENT RULES NOW LIVE IN ONE PLACE: `pup-bridge-dev` → THE RULES (R1-R5).** This file+ governs the user-vs-pup FIGHT (who may raise, who may demote); it defers to those for geometry.+ If you find any text anywhere that permits an off-screen window, it is stale — delete it. 2. **User intent is read from the OS, atomically, inside the demotion primitive.** `GetForegroundWindow() == our hwnd` AND `GetLastInputInfo` idle < 5s means a HUMAN raised it (a keyboard/mouse was active). A programmatic self-raise happens with idle hands. This check@@ -69,8 +77,12 @@ loaded gun pointed at the user's click. - **Any hide puts its re-show in a `finally`,** so a throw cannot strand the window; - **the caller verifies the window came back,** because a KILLED process runs no finally — verification has to live where the kill cannot reach;- - **a 120s sweep asserts the invariant for the whole population** (`sweepVisibility`), so any+ - **a 30s sweep asserts the invariant for the whole population** (`sweepVisibility`), so any future path that hides a window is repaired whether or not its author remembered this page.+ It is a BACKSTOP, not the fix. John, when told a stranded window was recovered in 50s:+ *"50 seconds is a fucking lifetime. the user clicks a pup icon within 500ms if they really+ want to see what's going on in that window."* If you ever find yourself sizing this interval,+ you are working on the wrong end of the problem — go make the bad state unreachable instead. The re-show restores the state we FOUND: `SW_SHOWMINNOACTIVE` for a window the user had minimized, `SW_SHOWNOACTIVATE` otherwise. The old code always used the latter, so every nudge silently un-minimized windows John had deliberately put away.
dev-skills/pup-window-targeting/SKILL.md+14−3@@ -59,9 +59,20 @@ Resolution is READ-ONLY. It never resizes or moves a window (see the wiggle grav 1. **Cached `session._hwnd`**, validated by `hwndBelongsToPup` (rejects a stale/recycled/foreign handle - e.g. one now owned by Edge or another profile's process).-2. **Birth-time capture** - `createPageInNewWindow` births each window at a UNIQUE off-screen position- and reads its handle THERE, while it is off-screen and small, before park maximizes it. That is the- only moment a same-profile window is unambiguous. Stashed on `page.__pupHwnd`, trusted first.+2. **Birth-time capture** - `createPageInNewWindow` resolves the handle in the SAME LOCKED MOMENT it+ creates the window, and stashes it on `page.__pupHwnd`. Trusted first, and it must be EXACT: rule+ R4 of the placement rules in `pup-bridge-dev` makes never losing track of a window a requirement,+ not a best effort.+ **The mechanism changed on 2026-08-20 and the reason matters.** It used to birth each window at a+ UNIQUE OFF-SCREEN coordinate and match on that coordinate, because same-profile windows otherwise+ share an identical rect. That worked, but it meant IDENTITY was the thing forcing every window to+ be created somewhere the user could not click - and that is exactly how windows ended up stranded+ off-screen behind live taskbar buttons. Identity must never again dictate placement.+ The replacement needs no unique coordinate: hold `withBirthLock`, snapshot the top-level windows+ owned by THIS profile's Chrome PIDs, create the window, snapshot again, and the single new handle+ is ours. The lock guarantees exactly one pup birth at a time; the PID filter excludes anything the+ user opens in the same instant. That is exact even though every window is now born at the same+ on-screen rect. 3. **Persisted handle** - `_hwnd` is written to the session file and restored on EVERY adoption path (`recoverSessions` AND `rescanProfile`). A Chrome window keeps the SAME hwnd across a bridge respawn, so a recovered window restores its handle instead of re-resolving.