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
Update 5 file(s)
5 files changed
+82−6
docs/PLAN-rust-port.md+6−1@@ -91,7 +91,12 @@ John's words. Written 2026-09-05 against pup 2.0.396; John approved the plan the asks the human for a password again. The pup demo tour: `tools/pup-demo.py` (eight beats through ab's demo kit, recorded with pup's own screen recorder) and the dashboard's Play-the-demo prompt now carry the same script; the first full take on arav-rog ran end to end (Google to its 2FA wall,- honestly; UltraLibrarian fully signed in). Jump lists: the two-right-clicks flash is ab#155+ honestly; UltraLibrarian fully signed in). Take 1 failed the demo-review tile (the mouse arrow parked on+ the taskbar button for a minute; no progress fill). Take 2 fixed the driver (mouse parked at the screen+ edge, window born in the background then raised by a REAL click on its taskbar button, which is also the+ only thing that clears the Windows 11 attention tint) and measured pup-bridge#99: Windows 11 25H2 (build+ 26200) paints NO taskbar progress for any app, plain COM on Notepad included; the skills say so now.+ The recorder HUD shows once the last window closes, so the closing line minimizes it and captions top. Jump lists: the two-right-clicks flash is ab#155 (registry-only AUMIDs, no anchoring shortcut); `aumidShortcut` (opt-in) asks ab for the anchor; human verification pending. Filed: ab#152 (kind:exe seed npm), ab#155 (jump-list anchor), ab#157 (forward bridge consent prompts into ah for boxes that block toasts).
skills/pup-settings/SKILL.md+2@@ -83,6 +83,8 @@ first, THEN it foregrounds. | Key | Default | What it does | |---|---|---| | `taskbarActivity` | **progress** | How pup signals it is WORKING on a window, on that window's taskbar button: `progress` = a determinate progress bar sized to the operation's running-average time (icon-apply on open, screenshots); `flash` = the old orange highlight (on open + on a mutating verb); `off` = nothing. This ONE control replaces the former `openFlash`, `autoFlash`, and `agentActivity` toggles. Full detail in the `pup-taskbar-progress` skill. |++Measured 2026-09-05 (pup-bridge#99): Windows 11 25H2 (build 26200) draws no taskbar progress fill for ANY app, and a background-born window's button keeps the Windows "needs attention" tint until a real user click. The verbs still run; do not promise the pixels. | `fgCaption` | **on** | When an AI brings a window to your screen, show its stated reason as an on-screen caption. | | `fgCaptionMs` | **2000** | How long (ms) that foreground-reason caption shows. | | `notMineCaption` | **off** | The single self-foreground caption control (v2.0.292: folded in the old `fgWatch`). Off by default. When on, pup watches for ~20s after a background open/navigate and, if a window pulls ITSELF to the front (JS/focus/Chrome, not pup), briefly captions that Adom did NOT do it. The window is no longer re-parked — off-screen birth means pup no longer needs a foreground-steal watchdog; this is now purely the "not me" caption. |
skills/pup-taskbar-progress/SKILL.md+13@@ -76,3 +76,16 @@ It records the real duration to `recordTiming('my-op', ms)` so the estimate self multi-phase async flow whose end is not a single awaited promise (like icon-apply), use the `startIconApplyProgress(sessionId)` / `markIconApplied(sessionId)` pattern: start the bar at the earliest point, and call the mark when the flow's final step confirms.++## Measured limits (2026-09-05, arav-rog, Windows 11 25H2 build 26200.9168), pup-bridge#99++- **Some Windows 11 builds draw NO taskbar progress fill, for any app.** On this build `pup_progress`+ (30/50/75%, indeterminate, error), ab `desktop_taskbar` on a stock Chrome for Testing button and on+ Notepad, and a plain PowerShell `ITaskbarList3` COM call on Notepad all reported success and painted+ nothing. So never promise the pixels in a demo or a caption: say the verb ran, and if the user must SEE+ it, screenshot the taskbar and read it before claiming a bar is there.+- **A background-born window wears the Windows 11 "needs attention" tint** (salmon button, red underline)+ because the OS flashes a window it refused to foreground. `desktop_flash_window {stop:true}` and a+ programmatic raise do NOT clear it; only a real user click on the taskbar button does. While tinted,+ no progress state is visible even on a build that draws it. A demo that wants a clean button opens+ the window in the background and then clicks its taskbar button (a user gesture), see `tools/pup-demo.py`.
skills/pup-taskbar-states/SKILL.md+13@@ -103,3 +103,16 @@ it has not applied. touch the fragile placement/resolution path for this edge unless John reopens the decision. Backoff makes it self-limiting (5 misses → 30-min retries), and a reopen fixes it immediately.++## Measured limits (2026-09-05, arav-rog, Windows 11 25H2 build 26200.9168), pup-bridge#99++- **Some Windows 11 builds draw NO taskbar progress fill, for any app.** On this build `pup_progress`+ (30/50/75%, indeterminate, error), ab `desktop_taskbar` on a stock Chrome for Testing button and on+ Notepad, and a plain PowerShell `ITaskbarList3` COM call on Notepad all reported success and painted+ nothing. So never promise the pixels in a demo or a caption: say the verb ran, and if the user must SEE+ it, screenshot the taskbar and read it before claiming a bar is there.+- **A background-born window wears the Windows 11 "needs attention" tint** (salmon button, red underline)+ because the OS flashes a window it refused to foreground. `desktop_flash_window {stop:true}` and a+ programmatic raise do NOT clear it; only a real user click on the taskbar button does. While tinted,+ no progress state is visible even on a build that draws it. A demo that wants a clean button opens+ the window in the background and then clicks its taskbar button (a user gesture), see `tools/pup-demo.py`.
tools/pup-demo.py+48−5@@ -67,28 +67,49 @@ class Demo: # ── beats ── def beat1_window(self): self.stop(0)- self.cap('This is a pup window: a real Chrome for Testing, in a profile pup owns, driven by the AI over CDP. It opens in the background by default; today it comes forward because we are watching.', 9000)- r = self.ab('pup_open_window', {'window': 'pup-demo', 'url': WIKI, 'owner': THREAD, 'foreground': True, 'foregroundReason': 'the pup demo tour: the user asked to watch pup on this screen'}, timeout=150)+ self.cap('This is a pup window: a real Chrome for Testing, in a profile pup owns, driven by the AI over CDP. It opens in the BACKGROUND: nothing steals your screen, the taskbar button just appears.', 9000)+ r = self.ab('pup_open_window', {'window': 'pup-demo', 'url': WIKI, 'owner': THREAD}, timeout=150) self.hwnd = r.get('hwnd'); print(f' window hwnd={self.hwnd} browser={ (r.get("browser") or {}).get("kind") }')+ self.wait(3)+ # Bring it up the way a user does: ONE real click on its taskbar button. That click is also the+ # only thing that clears the Windows 11 attention tint a background-born button wears (a tinted+ # button hides its progress bar; measured 2026-09-05). Fallback: pup's own raise.+ self.cap('Click its taskbar button and it comes up. That is the only time this tour takes the front.', 5000)+ rect = self.taskbar_button()+ if rect:+ self.ab('desktop_click', {'space': 'screen', 'x': rect.get('centerX', rect['x'] + rect['w'] // 2), 'y': rect.get('centerY', rect['y'] + rect['h'] // 2), 'reason': 'the pup demo tour: bring the demo window up by its taskbar button, as a user would'})+ else:+ self.ab('pup_raise_os_window', {'window': 'pup-demo', 'foregroundReason': 'the pup demo tour: the user asked to watch pup on this screen'})+ self.wait(1.5) # one big centred window: 98% of the primary work area mons = self.ab('desktop_list_monitors', {}); ms = mons.get('monitors') or [] wa = next((m.get('workArea') for m in ms if m.get('primary')), None) or (ms[0].get('workArea') if ms else None) if wa and self.hwnd: w, h = int(wa['width'] * 0.98), int(wa['height'] * 0.98); x, y = wa['x'] + (wa['width'] - w) // 2, wa['y'] + (wa['height'] - h) // 2 self.ab('desktop_set_window_bounds', {'hwnd': self.hwnd, 'x': x, 'y': y, 'w': w, 'h': h})- self.ab('desktop_cursor', {'action': 'hide'})+ self.park_mouse() self.wait(3); self.shot('window') self.say('Notice the toolbar: pup ships small extensions that ride every launch, and the owner chip bottom-left names the AI thread that owns this window.', 8000) def beat2_taskbar(self): self.stop(1) self.say('Look at the taskbar. That button is not "Google Chrome for Testing": pup stamps it with the AI thread name on the Adom tile, plus the page favicon as a badge. Every thread gets its own button.', 9000)- self.cap('A long task paints a progress bar on the button, so you see the work without opening the window.', 6000)+ # pup paints 30% / 75% / marquee / off through ab's ITaskbarList3. Measured 2026-09-05: Windows 11 25H2+ # (build 26200) on arav-rog renders NO taskbar progress for ANY app (plain COM on Notepad included), so+ # the caption states the capability without promising the pixels; on a box that renders it, it shows.+ self.cap('A long task can paint a progress bar on that button, so you see the work without opening the window. Some Windows builds no longer draw taskbar progress; the verb still runs.', 7000) self.ab('pup_progress', {'window': 'pup-demo', 'mode': 'value', 'value': 30, 'note': 'demo: 3 of 10 pages'}); self.wait(2.5) self.ab('pup_progress', {'window': 'pup-demo', 'mode': 'value', 'value': 75, 'note': 'demo: 8 of 10 pages'}); self.wait(2.5) self.ab('pup_progress', {'window': 'pup-demo', 'mode': 'indeterminate', 'note': 'demo: pulsing'}); self.wait(2.5) self.ab('pup_progress', {'window': 'pup-demo', 'mode': 'off'}); self.wait(1) + def hud_hwnd(self):+ """The pup recorder HUD window (title 'Adom Bridge Recorder'), or None."""+ r = self.ab('desktop_list_windows', {}); ws = r.get('windows') or (r.get('data') or {}).get('windows') or []+ for w in ws:+ if 'Adom Bridge Recorder' in str(w.get('title') or ''): return w.get('hwnd')+ return None+ def tray_hwnd(self): if getattr(self, '_tray', None): return self._tray ps = 'Add-Type -Namespace W -Name U -MemberDefinition \'[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern IntPtr FindWindow(string c, string t);\'\n$h = [W.U]::FindWindow("Shell_TrayWnd", $null); "TRAY_HWND=" + [int64]$h\n'@@ -169,9 +190,31 @@ class Demo: self.stop(7) self.say('Last: the jump list task "Close ALL Adom Pup windows". One click there, or the same verb from an AI, and every pup window on this desktop is gone. Nothing left on your desktop.', 7000) self.ab('pup_close', {'all': True, 'everyone': True}); self.wait(3); self.screen('after-close-all')- self.say('That is pup. Every window, tab, login and screenshot you saw was the AI driving a browser as you, without ever taking your desktop away.', 8000)+ # The recorder HUD (a CfT window pup keeps alive for the take) becomes visible once the demo window+ # is gone, bottom-right, exactly where the bottom caption sits (take 2 review, 2026-09-05). Close the+ # HUD's cover first: minimize it for the closing line, then the caption goes top so nothing overlaps.+ hud = self.hud_hwnd()+ if hud: self.ab('desktop_set_window_state', {'hwnd': hud, 'state': 'minimize', 'reason': 'the pup demo tour: keep the recorder HUD out of the closing shot'})+ print(f' ▶ closing line'); self.ab('desktop_caption', {'id': 'pup-demo', 'text': 'That is pup. Every window, tab, login and screenshot you saw was the AI driving a browser as you, without ever taking your desktop away.', 'duration': 8000, 'position': 'top', 'size': 'large'}); self.wait(5)++ def park_mouse(self):+ """Put the REAL mouse at the right screen edge (outside the 98% window) and hide ab's glow cursor.+ A take showed the arrow parked on the taskbar button for a full minute (demo-review, 2026-09-05)."""+ mons = self.ab('desktop_list_monitors', {}); ms = mons.get('monitors') or []+ m = next((m for m in ms if m.get('primary')), ms[0] if ms else None)+ if m:+ b = m.get('bounds') or m.get('rect') or m; x = int(b.get('x', 0) + b.get('width', b.get('w', 2560)) - 2); y = int(b.get('y', 0) + b.get('height', b.get('h', 1440)) // 2)+ self.ab('desktop_hover', {'space': 'screen', 'x': x, 'y': y, 'reason': 'park the mouse at the screen edge so it never sits on what the tour shows'})+ self.ab('desktop_cursor', {'action': 'hide'})++ def pre_take(self):+ """A clean desktop before the recorder starts: no leftover pup windows or idle Chromes (their+ taskbar buttons sit in the Windows 11 attention state, which hides the progress bar)."""+ self.ab('pup_close', {'all': True, 'everyone': True}); self.ab('pup_quit_idle_browsers', {}); time.sleep(2)+ self.park_mouse() def run(self, beats):+ self.pre_take() self.rec_start() try: for n in beats: getattr(self, ['beat1_window','beat2_taskbar','beat3_jumplists','beat4_toolbar_annotate','beat5_google','beat6_screenshots','beat7_ultralibrarian','beat8_close_all'][n])()