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
dev-skills: the birth-lock regression post-mortem (threads sharing one pup window handle) + the R4 cross-reference for the extension lane
2 files changed
+75
dev-skills/pup-bridge-dev/SKILL.md+40@@ -389,6 +389,18 @@ moment the window is created and stashed on the page. See `pup-window-targeting` 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). +**THE LOCK IS THE LOAD-BEARING HALF, AND THE EXTENSION LANE IS WHERE DROPPING IT SHOWS (2026-09-14).**+The Rust port kept the set-diff and dropped `withBirthLock`. Nothing fails a single-threaded test: two+threads each diff against their own `before`, both see the one new frame, and both claim it. On the ext+lane (ONE Chrome holding MANY threads' windows) three sessions ended up on hwnd 590484, each stamping its+AUMID over the last, so whoever opened last owned the button and every other thread's identity was erased.+Two more rules that fell out of it, both already implied by `pup-window-targeting` and now enforced in+code: a handle another session already owns is NEVER a candidate, and the "sole window of a profile"+shortcut is invalid on a lane where one profile holds many threads' windows. Class `Chrome_WidgetWin_1`+is also Edge's, so an unfiltered diff can stamp pup's tile onto the user's own browser: filter births to+the lane's own Chrome pids. Full post-mortem in `pup-window-targeting`. The standing test: open N windows+from N threads CONCURRENTLY and assert N distinct hwnds. Sequential opens cannot see this bug.+ **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@@ -1696,3 +1708,31 @@ John's radar for a misbehaving pup. The replacement is mandatory and already wir changelog. A loop you added that is not in the panel is a bug (it is untracked). - Never add a PowerShell spawn to a loop. `src/win32.js` (koffi) is the in-process path; if a Win32 call is missing there, add it there.+++## 2026-09-14: what moved into the binary, and how the extension lane grew up++- **Thread tile**: `pup_identity::tile` (fontdue, Familjen Grotesk Bold as TTF, the same fit-to-plate layout+ as the old in-page script, every ICO entry at its own pixel size; verified pixel-identical). No page, no+ lane, no CSP: the tile exists at birth.+- **Overlay badge**: `pup_identity::badge` (the `image` crate decodes PNG/ICO/JPEG/GIF/BMP/WebP; SVG+ favicons fall through to the generic glyph, which is an embedded PNG). The icon is sourced per lane+ (`identity::source_favicon`): the extension's `favicon` action reads `<link rel=icon>` by INJECTED+ FUNCTION (CSP-exempt, unlike eval) and fetches with the page's cookies; CDP reads the hrefs over+ `Runtime.evaluate`; `/favicon.ico` is the fallback with the 30 min negative cache.+- **Birth watcher shared** (`birth_watch` / `birth_report` in lib.rs): both opens register the AUMID ahead+ of time and stamp the newborn natively while it is invisible. The ext lane's own bind loop is gone; the+ watcher's hwnd is the window, matched per session by title when several threads share the lane browser+ (`extdrive::lane_hwnd_for`), never guessed.+- **Extension channel is a queue** (`extdrive.rs`): commands by id, results by id, in-flight re-queued on a+ fresh hello (an MV3 worker can die mid-command), the worker drains up to 12 commands per poll. The old+ one-slot channel made concurrent callers time each other out.+- **Annotate on both lanes**: the overlay is ONE file, `src/extension/annotate-overlay.js`, file-injected on+ the ext lane and `include_str!`-evaluated on CDP; start/poll/done go through `transport::inject_script`,+ `call_window_fn`, `read_window`, `capture_viewport_png`; the caption strip and preview are composed in+ the binary (`badge::annotate_compose`). No `innerHTML` in the overlay: Trusted-Types pages refuse it.+- **Worker self-reload**: `extdrive::reload_stale_worker` on the health tick re-stages the extension folder+ and sends `reload`; the worker answers first, then `chrome.runtime.reload()`. Workers older than 2.0.569+ do not know `reload`, so the very first upgrade to that line still needs one lane cycle.+- **Orphan `about:blank` windows** from a lane relaunch are swept after every ext open and on the tick.+- **Rule of the day (John)**: pup is the extension lane. CDP opens only on an explicit `lane:"cdp"`.
dev-skills/pup-window-targeting/SKILL.md+35@@ -81,6 +81,41 @@ Resolution is READ-ONLY. It never resizes or moves a window (see the wiggle grav 5. If none of the above: **leave it BARE** (no overlay). Bare > wrong, forever (John's Edge-overlay rule). NEVER guess. +## THE RUST PORT DROPPED THE LOCK, AND IT COST EXACTLY WHAT THIS FILE PREDICTS (2026-09-14)++The Node implementation held `withBirthLock` around the set-diff. The Rust port's `birth_watch` kept the+diff and **silently dropped the lock**. Nothing in the type system notices, and a single-threaded test never+shows it, so it shipped and ran for weeks.++What John saw: he had the all-engineering thread open a pup window, then NSF open one, and each thread's+taskbar button wore the OTHER thread's tile. *"EVERY FUCKING AI-THREAD IS SUPPOSED TO HAVE ITS OWN PUP+WINDOW. why the fuck did you regress?"* Measured live: three sessions (`pup-wiki`/pup-bridge,+`nsf-pages`/NSF, `all-engineering-agenda`/all-engineering) all carried **hwnd 590484**, and the log showed+each one stamping its AUMID onto that one handle in turn. Last stamp wins, so whoever opened last owned+the button and everyone else's identity was erased.++Three independent defects, all of which this file already forbade:++1. **No lock.** Two threads each snapshot their own `before`, both create a window, both diff, and both see+ the same new frame. The lock is not an optimization, it is the thing that makes the diff *mean* anything.+2. **No `taken` exclusion.** A handle another session already owns was still a candidate. `readopt_ext` had+ this check and never produced the bug; `lane_hwnd_for` and the birth path lacked it and did.+3. **The sole-window shortcut applied per session.** Rule 4 above says "sole window **of a profile**". The+ extension lane is ONE profile holding MANY threads' windows, so `if wins.len() == 1 { return it }` handed+ the same window to every session that asked. That is the "guess" rule 5 bans, wearing a rule-4 costume.++**The extension lane makes this class WORSE, not better, and here is why.** The CDP lane gives every window+its own browser target and its own `windowId` at creation. The ext lane drives one Chrome through an+extension, so "the lane window" is genuinely ambiguous and the only cheap discriminator is the active tab+TITLE, which is the mechanism this entire file exists to ban. Two windows on the same page are+indistinguishable by title. So on the ext lane: bind at birth under the lock, persist Chrome's own+`windowId` alongside the hwnd, and when neither is available **leave it BARE**. A refusal costs one+unbranded button for a few seconds. A guess costs another thread its identity, silently.++**The invariant to test, forever:** open N windows from N different threads and assert the hwnds are N+distinct values. One shared hwnd anywhere in that table is this bug, whatever the icons happen to look like+at that instant.+ ## ⚰️ The width-wiggle is DEAD. Do not resurrect it. For months, same-profile windows (which share the identical parked rect) were disambiguated by