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 dev skill pup-taskbar-identity: the birth-hwnd capture architecture (serialized set-diff, position-independent) and the mandatory ralph test (ralph_icons.sh) that must pass before claiming any taskbar-identity fix; records the ban on position-matching and log-only proof. Baseline: two warm 18-window runs, 0 failures each
2 files changed
+121
dev-skills/pup-taskbar-identity/SKILL.mdadded+65@@ -0,0 +1,65 @@+---+name: pup-taskbar-identity+description: DEV skill (source-only) - how pup maps each window to its OS taskbar button (AUMID tile + overlay badge), the hard-won architecture, and the RALPH TEST you must run before claiming any taskbar-identity fix works. Read before touching birth-hwnd capture, resolveSessionHwndByBounds, hwndBelongsToPup, stampPupIdentity, or applyAppOverlay.+---++# pup taskbar identity: birth-hwnd capture + the ralph gate++Every pup window must present as its own teal "Adom Pup" taskbar button (per-session AUMID) with a+favicon overlay badge. All pup windows share ONE Chrome process (the `adom-you` profile), so the+only way to address a specific window for the AUMID stamp and overlay is its **OS window handle+(hwnd)**. Getting that handle reliably is the entire problem, and it has broken ~a dozen times.++## THE RALPH RULE (John, 2026-08-09: "you need to do your own ralph test")++A SINGLE open looking right proves nothing — every taskbar-identity regression here passed a+one-window spot check and failed under repetition/concurrency. Before claiming ANY fix works, run+`ralph_icons.sh` (in this folder): 9 rounds cycling single / concurrent-3 / multi-tab-pair opens,+asserting each window gets a DISTINCT handle + a successful `stamped appId` + an `overlay PAINT/+ENFORCED`, and that no two sessions ever share a handle (COLLISION). Warm the bridge first+(`pup_readiness` until ok) — the first 1-2 rounds after a `bridge_install` respawn fail on ab+cold-start flakiness, NOT icon logic, and will mislead you. A clean run is 18/18 windows, 0 failures,+18 distinct handles. Baseline proof (2.0.39): two consecutive warm runs, 0 failures each.++```bash+bash dev-skills/pup-taskbar-identity/ralph_icons.sh 9+```++## How handle capture actually works (2.0.38, the version that finally held)++1. **Every** pup window on Windows is created via `createPageInNewWindow` (CDP+ `Target.createTarget {newWindow:true}`) — including the FIRST window on a profile. (Pre-2.0.37 the+ first window used `browser.newPage()`, which captures no handle; that window was permanently+ unresolvable and showed a raw Chrome button. THE bug John kept catching.)+2. Capture is a **serialized set-diff**, NOT position matching:+ - A global `_birthLock` serializes creation so only one window is born at a time.+ - Snapshot all Chrome `Chrome_WidgetWin_1` handles BEFORE `createTarget`; the ONE new handle+ after (not in the snapshot, not owned by another live session) is this window's, wherever+ Chrome placed it. **Do not identify the window by position** — Chrome clamps the -32000 birth+ coordinate on-screen on some boxes, so position matching finds nothing or cross-wires (the+ fusion+nb-shared-handle collision).+3. `hwndBelongsToPup` trusts a birth-captured handle as proof by construction (no title tag, no pid+ membership). `hwndOwnedByOtherSession` guards every resolve path so two sessions can never share+ one handle.+4. Only then does `stampPupIdentity` set the per-session AUMID (creates the teal button) and+ `applyAppOverlay` paint the favicon badge — deferred until the handle exists, stamp-once then+ overlay-once (see the AUMID/overlay ordering in server.js).++## Bans / traps (each shipped and hurt)++- **Never identify a window by screen position** — Chrome clamps off-screen births; positions+ collide under concurrency. Set-diff by handle only.+- **Never remove the title tag without replacing its handle-resolution role** — 2.0.24 killed the+ title tag purely as user-facing cosmetics, silently removing the shared-profile fallback that the+ stamp AND `hwndBelongsToPup` relied on. That single change caused weeks of raw-Chrome icons.+- **Never trust a "stamped ✓" log as proof the tile is right** — the log says the AUMID call+ succeeded; it does NOT prove Windows repainted the button or that the handle was the RIGHT window.+ Verify with the ralph handle-distinctness + a real taskbar screenshot.+- **Warm the bridge before asserting** — cold-start (post-install/idle respawn) flakiness is an ab+ lifecycle issue (ab#61), not icon logic; it produces false failures in rounds 1-2.++## Known remaining gap++Cold-start: the first 1-2 windows opened immediately after a bridge respawn can miss their handle+(ab wedge / slow spawn). `refresh_bridges` + a warm `pup_readiness` clears it. Tracked as the ab+lifecycle work; pup's own capture is clean once warm (see the ralph baseline).
dev-skills/pup-taskbar-identity/ralph_icons.shadded+56@@ -0,0 +1,56 @@+#!/bin/bash+# Ralph test for pup taskbar identity: repeatedly open windows (single, concurrent, multi-tab),+# assert EVERY window gets a distinct OS handle + a successful AUMID stamp + an overlay paint,+# and NO collision (two sessions on one handle) and NO stamp FAILED. Closes what it opens each round.+TGT=AdomLapper+LOG=C:/Users/john/.adom/bridge-logs/puppeteer.log+ROUNDS=${1:-8}+ab() { adom-bridge-cli --target $TGT "$@" 2>&1; }+jget() { python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('$1',''))" 2>/dev/null; }++total_windows=0 fail=0+declare -A allhandles+for r in $(seq 1 $ROUNDS); do+ mode=$((r % 3))+ case $mode in+ 1) threads=("rl-$r-a") ;; # single+ 2) threads=("rl-$r-a" "rl-$r-b" "rl-$r-c") ;; # concurrent burst+ 0) threads=("rl-$r-a" "rl-$r-b") ;; # pair+ esac+ # open (concurrent when >1)+ for t in "${threads[@]}"; do+ ab pup_open_window '{"url":"https://example.com","foreground":false}' --ai-thread "$t" >/dev/null 2>&1 &+ done+ wait+ # round 0: also add tabs to make them multi-tab+ if [ $mode -eq 0 ]; then+ for t in "${threads[@]}"; do+ ab pup_open_tab '{"url":"https://wiki.adom.inc/adom/pup-bridge"}' --ai-thread "$t" >/dev/null 2>&1+ done+ fi+ sleep 13+ # pull the log ONCE for this round and assert+ ab pull_file "{\"filePaths\":[\"${LOG//\//\\\\}\"],\"saveTo\":\"/tmp/ralphlog\"}" --ai-thread "pup-bridge build" >/dev/null 2>&1+ LF=/tmp/ralphlog/puppeteer.log+ for t in "${threads[@]}"; do+ sid="ai-$(echo "$t" | tr 'A-Z' 'a-z')"+ total_windows=$((total_windows+1))+ handle=$(grep -E "\"$sid\" resolved OS handle [0-9]+" "$LF" 2>/dev/null | tail -1 | sed -E 's/.*handle ([0-9]+).*/\1/')+ stamped=$(grep -cE "\"$sid\" stamped appId" "$LF" 2>/dev/null)+ failed=$(grep -cE "\"$sid\".*stamp FAILED" "$LF" 2>/dev/null)+ overlay=$(grep -cE "\"$sid\".*(PAINT|ENFORCED)" "$LF" 2>/dev/null)+ problem=""+ [ -z "$handle" ] && problem="$problem NO-HANDLE"+ [ "$stamped" = "0" ] && problem="$problem NOT-STAMPED"+ [ -n "$handle" ] && [ -n "${allhandles[$handle]}" ] && [ "${allhandles[$handle]}" != "$sid" ] && problem="$problem COLLISION-with-${allhandles[$handle]}"+ [ "$overlay" = "0" ] && problem="$problem NO-OVERLAY"+ [ -n "$handle" ] && allhandles[$handle]=$sid+ if [ -n "$problem" ]; then echo " r$r $sid hwnd=$handle stamp=$stamped fail=$failed ovl=$overlay ->$problem"; fail=$((fail+1)); fi+ done+ # cleanup this round's windows+ for t in "${threads[@]}"; do ab pup_close_window '{}' --ai-thread "$t" >/dev/null 2>&1; done+ sleep 2+ echo "round $r ($([ $mode -eq 1 ] && echo single || ([ $mode -eq 2 ] && echo concurrent-3 || echo pair-multitab))): ${#threads[@]} windows, running fails=$fail"+done+echo "===================="+echo "RALPH DONE: $total_windows windows across $ROUNDS rounds | FAILURES: $fail | distinct handles: ${#allhandles[@]}"