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.
master
John Lauer
Gates run beside other threads' windows (own profile, untouched); smoke expects no owner chip by default
f38b7d9
29d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
#!/usr/bin/env python3
"""Ralph test for the jump-list tasks, THROUGH THE REAL PATH: Explorer launches ab's windowless shim
(ab-taskrun.exe) with the link's target and arguments; this script runs exactly that command line on the
box for every task pup registers, then asserts the effect in the window. John, 2026-09-06: "have you ralph
tested your jump list items? i just tried annotate and it doesn't work" (the Rust port refused every
window-targeting task with not_your_window; a by-hand exe call of pup_dashboard had passed for nothing).
tools/jumplist-ralph.py --target arav-rog
"""
import argparse, base64, json, subprocess, sys, time
THREAD = "jl-ralph"
WIKI = "https://wiki.adom.inc/adom/pup-bridge"
def ab(target, verb, args, thread=THREAD, timeout=120):
p = subprocess.run(['adom-bridge', '--target', target, '--ai-thread', thread, verb, json.dumps(args)], capture_output=True, text=True, timeout=timeout + 30)
try: d = json.loads(p.stdout)
except Exception: return {'error': (p.stdout + p.stderr)[-300:]}
o = d.get('output'); inner = json.loads(o) if isinstance(o, str) and o.startswith('{') else {}
return {**inner, **d}
def ps(target, script, reason):
r = ab(target, 'shell_execute', {'command': 'powershell -NoProfile -Command "' + script.replace('"', '\\"') + '"', 'timeoutSeconds': 90, 'reason': reason})
return (r.get('stdout') or r.get('error') or '').strip()
def b64(o): return base64.b64encode(json.dumps(o, separators=(',', ':')).encode()).decode()
def main():
ap = argparse.ArgumentParser(); ap.add_argument('--target', required=True); a = ap.parse_args(); t = a.target
# 2.0.700: this gate fires the real "Close ALL Adom Pup windows" task and closes every window between checks
# (pup_close everyone), which takes other threads' windows with it. A box where another thread has windows is
# not a valid box: refuse (INVALID, exit 2) and name them, as tools/foreground-proof.sh does.
others = [f"{s.get('sessionId')}({s.get('owner')})" for s in (ab(t, 'pup_status', {}).get('sessions') or []) if s.get('owner') not in (THREAD, 'user-taskbar-menu')]
# 2.0.714 (John: "what if you use crr and arav-rog for your tests?"): on a box where other threads have windows,
# run SHARED: the gate's windows live in their own profile (their own browser), closes touch only the gate's own
# windows, and the one check that must close everyone's windows (the real Close ALL task) is skipped and said so.
shared = bool(others)
if shared: print(f"SHARED MODE: other threads have pup windows on {t} ({' '.join(others)}); the gate runs in its own profile and leaves them alone")
prof = {'profile': 'jlralph'} if shared else {}
close_mine = {'all': True} if shared else {'all': True, 'everyone': True}
exe = ps(t, "Get-ChildItem (Join-Path $env:LOCALAPPDATA 'Adom Bridge\\bridges-cache\\puppeteer') -Filter *.exe | Select-Object -First 1 -ExpandProperty FullName", 'jump-list ralph: locate the pup exe')
shim = ps(t, "Join-Path $env:LOCALAPPDATA 'Adom Bridge\\ab-taskrun.exe'", 'jump-list ralph: locate the taskrun shim')
print(f'exe={exe}\nshim={shim}')
results = []
def fire(verb, args, reason):
# EXACTLY what the shell link holds: shim "target" --jumplist <verb> <b64> (window_identity.rs quotes the target)
cmd = f"& '{shim}' '{exe}' --jumplist {verb} {b64(args)}; 'exit=' + $LASTEXITCODE"
return ps(t, cmd, reason)
def check(name, ok, detail=''):
results.append((name, ok)); print(f' {"PASS" if ok else "FAIL"} {name} {detail}')
ab(t, 'pup_close', close_mine); time.sleep(1)
r = ab(t, 'pup_open_window', {'window': 'jl-ralph', 'url': WIKI, 'owner': THREAD, **prof}, timeout=150)
sid = r.get('sessionId') or 'jl-ralph'; print(f'window {sid} hwnd={r.get("hwnd")}'); time.sleep(6) # identity + jump list committed
# 1. Annotate this page
print('\n== Annotate this page'); out = fire('pup_annotate', {'sessionId': sid, 'tool': 'pen'}, 'jump-list ralph: Annotate task through the shim'); time.sleep(3)
e = ab(t, 'pup_eval', {'window': sid, 'expr': '!!window.__pupAnnotate'})
lau = (ab(t, 'pup_list_windows', {}).get('sessions') or [{}])
lau = next((s.get('lastAgentUpdate') or {} for s in lau if s.get('sessionId') == sid), {})
check('annotate overlay armed', e.get('result') is True, f'({out}; lastAgentUpdate={lau.get("verb")})')
# 2.0.444 (John: "'annotate this page' on the fusion taskbar icon did nothing"): a HUMAN caller's
# annotate must bring the window to the screen, not arm a pen on a window parked at the bottom.
wi = ab(t, 'pup_window_info', {'window': sid})
fg = ab(t, 'desktop_get_foreground', {})
fgh = fg.get('hwnd') or (fg.get('window') or {}).get('hwnd')
idle = (ab(t, 'desktop_user_idle_ms', {}) or {}); idle_ms = idle.get('idleMs') if isinstance(idle.get('idleMs'), int) else (json.loads(idle.get('output') or '{}').get('idleMs') if isinstance(idle.get('output'), str) else None)
raised_ok = bool(wi.get('foreground')) and (fgh is None or fgh == r.get('hwnd'))
elevated = ''
if not raised_ok:
probe = ab(t, 'desktop_bring_to_front', {'hwnd': r.get('hwnd'), 'reason': 'jump-list ralph: read why Windows refused the Annotate raise'})
if 'INTEGRITY' in (probe.get('error') or '').upper(): elevated = (json.loads(ab(t, 'desktop_get_foreground', {}).get('output') or '{}').get('title') or '?')
if not raised_ok and elevated:
print(f' SKIP annotate RAISED the window: the foreground belongs to an elevated process ({elevated!r}); Windows refuses a foreground switch away from it for every process, ab included')
elif not raised_ok and isinstance(idle_ms, int) and idle_ms < 200_000:
# Windows refuses a foreground switch while the user is active (foreground lock, ~200 s after input); a
# scripted raise from shell_execute cannot win that, by design. The click path (shim from the jump list) can.
print(f' SKIP annotate RAISED the window: someone used this box {idle_ms // 1000}s ago (foreground lock active); the raise refusal is Windows, not pup')
else:
check('annotate RAISED the window (user caller)', raised_ok, f'(session.foreground={wi.get("foreground")}, os fg hwnd={fgh}, window hwnd={r.get("hwnd")}, idleMs={idle_ms})')
ab(t, 'pup_eval', {'window': sid, 'expr': 'window.__pupAnnotate && window.__pupAnnotate.exit && window.__pupAnnotate.exit(); true'})
# 1b. The Adom TOOLBAR pen (2.0.453): worker.js sends only the tab URL and caller user-toolbar; pup must
# resolve the window from the URL (John, 2026-09-10: after a wake the pen did nothing).
print('\n== Toolbar pen by _tabUrl (worker.js payload)')
tabs = ab(t, 'pup_list_tabs', {'window': sid}); cur = ((tabs.get('tabs') or [{}])[0].get('url')) or WIKI
body = json.dumps({'command': 'pup_annotate', 'args': {'_tabUrl': cur, 'tool': 'pen', 'caller': {'aiThread': 'user-toolbar'}}})
out = ps(t, "$pf = Join-Path $env:LOCALAPPDATA 'Adom Bridge\\pup-bridge-port.json'; $port = (Get-Content $pf -Raw | ConvertFrom-Json).port; (Invoke-WebRequest -Uri ('http://127.0.0.1:' + $port + '/command') -Method Post -ContentType application/json -Headers @{'x-adom-caller-thread'='user-toolbar'} -Body '" + body.replace("'", "''") + "' -UseBasicParsing).Content", 'jump-list ralph: the toolbar pen payload (_tabUrl, user-toolbar)')
time.sleep(2); e = ab(t, 'pup_eval', {'window': sid, 'expr': '!!window.__pupAnnotate'})
started = ('started\\":true' in str(out)) or ('"started":true' in str(out)) # the reply is an escaped JSON envelope
check('toolbar pen resolved the window by _tabUrl and armed', e.get('result') is True and started, f'(url={cur[:60]}; started={started}; reply={str(out)[:400]})')
# 1b2. Esc closes the overlay (2.0.457, John: "esc key should let me turn it back off"): a real key event.
ab(t, 'pup_press_key', {'window': sid, 'key': 'Escape'}); time.sleep(1)
e2 = ab(t, 'pup_eval', {'window': sid, 'expr': '!window.__pupAnnotate'})
check('Esc closed the annotation overlay', e2.get('result') is True, f'(overlay gone={e2.get("result")})')
ab(t, 'pup_eval', {'window': sid, 'expr': 'window.__pupAnnotate && window.__pupAnnotate.exit && window.__pupAnnotate.exit(); true'})
# 1b3. Focus emulation follows the active tab (2.0.458, John: touchpad scrolling stuttering with 21 tabs).
# Every attached tab used to carry it, so no background tab was ever timer-throttled or discardable.
print('\n== Focus emulation is not left on every tab')
ab(t, 'pup_open_tab', {'window': sid, 'url': WIKI + '/releases'}); time.sleep(3)
rows = (ab(t, 'pup_list_tabs', {'window': sid}).get('tabs') or []) # list_tabs resolves the SESSION, so it does not perturb the state it reports
emulated = [x.get('tabId') for x in rows if x.get('focusEmulated')]
active = [x.get('tabId') for x in rows if x.get('active')]
check('only the active tab is focus-emulated', len(rows) >= 2 and emulated == active, f'(tabs={len(rows)}, emulated={emulated}, active={active})')
ab(t, 'pup_close', close_mine); time.sleep(1)
r = ab(t, 'pup_open_window', {'window': 'jl-ralph', 'url': WIKI, 'owner': THREAD, **prof}, timeout=150); sid = r.get('sessionId') or 'jl-ralph'; time.sleep(6)
# 1b4. A press must never be left down (2.0.460, John: "i still can't scroll with my touchpad on ANY of your
# pup windows... i also can't change tabs"). A release that failed mid-click left Chrome treating every later
# mouse event as one drag. The next mouse verb on the tab has to finish that click.
print('\n== A stuck mouse button is released before the next mouse verb')
ab(t, 'pup_eval', {'window': sid, 'expr': "window.__ups = 0; window.__downs = 0; addEventListener('pointerdown', () => window.__downs++, true); addEventListener('pointerup', () => window.__ups++, true); true"})
ab(t, 'pup_input_dispatch', {'window': sid, 'type': 'mousePressed', 'x': 40, 'y': 300, 'button': 'left'}) # a press with no release, exactly what a timed-out release leaves behind
d1 = ab(t, 'pup_eval', {'window': sid, 'expr': 'window.__downs + "/" + window.__ups'})
ab(t, 'pup_click', {'window': sid, 'x': 60, 'y': 320}) # any later mouse verb must release it first
d2 = ab(t, 'pup_eval', {'window': sid, 'expr': 'window.__downs + "/" + window.__ups'})
ups = int(str(d2.get('result') or '0/0').split('/')[1]); downs = int(str(d2.get('result') or '0/0').split('/')[0])
check('the orphaned press was released', ups >= downs and ups >= 2, f'(down/up after the orphan press: {d1.get("result")}, after the next click: {d2.get("result")})')
# 1b5. Every open tells the calling AI what it is spending (2.0.462, John: "if the ai knew how much ram it
# was using, that might have helped the ai decide... you should be automatically telling it what % of ram
# was even left on the box").
print('\n== Memory is reported back to the caller')
rt = ab(t, 'pup_open_tab', {'window': sid, 'url': WIKI + '/releases'}); time.sleep(2)
m = rt.get('memory') or {}
ok_mem = isinstance(m.get('boxFreePct'), int) and isinstance(m.get('boxTotalMb'), int) and m.get('boxTotalMb') > 0 and isinstance(m.get('_hint'), str) and str(m.get('boxFreePct')) in m.get('_hint', '')
check('open_tab reports box RAM and what a tab costs', ok_mem, f"(freePct={m.get('boxFreePct')}, browserMb={m.get('browserMb')}, avgMbPerTab={m.get('avgMbPerTab')}, tabs={m.get('tabsOnThisBrowser')})")
lw = ab(t, 'pup_list_windows', {}).get('memory') or {}
check('pup_list_windows reports it too', isinstance(lw.get('boxFreePct'), int), f"(freePct={lw.get('boxFreePct')})")
# 1b6. An idle browser is released (2.0.463, John: "if pup has no windows open, why is it even using 1.2gb
# across 8 processes?"). Set the grace to 1 min, close everything, and it must be gone within ~90 s.
print('\n== An idle browser is released')
ab(t, 'pup_configure', {'idleBrowserQuitMin': '1'})
ab(t, 'pup_close', close_mine); time.sleep(2)
gone = False
for _ in range(10):
time.sleep(15)
if shared:
# other threads keep the main browser busy: the gate's OWN browser (profile jlralph) must be the one that exits
n = ps(t, "(Get-CimInstance Win32_Process -Filter \"Name='chrome.exe'\" | Where-Object { $_.CommandLine -match 'pup-profiles[\\\\/]jlralph' }).Count", 'jump-list ralph: is the gate browser gone')
if n.strip() in ('', '0'): gone = True; break
continue
st = ab(t, 'pup_status', {})
m = st.get('memory') or {}
if not m.get('browserMb'): gone = True; break
check('the browser with no windows was closed', gone, '(pup_status no longer reports a browser for the profile)')
ab(t, 'pup_configure', {'idleBrowserQuitMin': '10'})
r = ab(t, 'pup_open_window', {'window': 'jl-ralph', 'url': WIKI, 'owner': THREAD, **prof}, timeout=150); sid = r.get('sessionId') or 'jl-ralph'; time.sleep(6)
# 1c. A tab the USER opens in a pup window (window.open from the page, outside pup) is adopted by that
# window's session within a health tick (2.0.453).
print('\n== User-opened tab adoption')
before = len(ab(t, 'pup_list_tabs', {'window': sid}).get('tabs') or [])
ab(t, 'pup_eval', {'window': sid, 'expr': "window.open('https://wiki.adom.inc/adom/pup-bridge/releases', '_blank'); true"})
time.sleep(3); after = ab(t, 'pup_list_tabs', {'window': sid}).get('tabs') or []
check('user-opened tab adopted into the window session', len(after) == before + 1 and any('releases' in (x.get('url') or '') for x in after), f'(tabs {before} -> {len(after)})')
ab(t, 'pup_close', close_mine); time.sleep(1)
r = ab(t, 'pup_open_window', {'window': 'jl-ralph', 'url': WIKI, 'owner': THREAD, **prof}, timeout=150); sid = r.get('sessionId') or 'jl-ralph'; time.sleep(6)
# 2. Switch to public view / logged-in view
print('\n== Switch to public view'); out = fire('pup_wiki_set_view', {'sessionId': sid, 'view': 'public'}, 'jump-list ralph: public view task'); time.sleep(6)
u = ab(t, 'pup_eval', {'window': sid, 'expr': 'location.href'}).get('result') or ''
check('public view url', 'view=public' in u, f'({out}; url={u[:70]})')
print('\n== Switch to logged-in view'); out = fire('pup_wiki_set_view', {'sessionId': sid, 'view': 'authed'}, 'jump-list ralph: logged-in view task'); time.sleep(6)
u = ab(t, 'pup_eval', {'window': sid, 'expr': 'location.href'}).get('result') or ''
check('logged-in view url', 'view=public' not in u and 'wiki.adom.inc' in u, f'({out}; url={u[:70]})')
# 3. Taskbar relaunch (the header row click): pup_focus_window
print('\n== Relaunch command (pup_focus_window)'); out = fire('pup_focus_window', {'sessionId': sid, 'foregroundReason': 'jump-list ralph: the relaunch command'}, 'jump-list ralph: relaunch command'); time.sleep(2)
lau = next((s.get('lastAgentUpdate') or {} for s in (ab(t, 'pup_list_windows', {}).get('sessions') or []) if s.get('sessionId') == sid), {})
check('focus reached pup', lau.get('verb') == 'pup_focus_window', f'({out}; lastAgentUpdate={lau.get("verb")})')
# 4. Open pup dashboard
print('\n== Open pup dashboard'); out = fire('pup_dashboard', {}, 'jump-list ralph: dashboard task'); time.sleep(5)
sess = [s.get('sessionId') for s in (ab(t, 'pup_list_windows', {}).get('sessions') or [])]
check('dashboard window exists', 'pup-dashboard' in sess, f'({out}; sessions={sess})')
# 5. Close ALL Adom Pup windows
print('\n== Close ALL Adom Pup windows')
if shared:
print(' SKIP all windows closed: this task closes EVERY pup window, other threads included; it runs only on a box with no other windows')
ab(t, 'pup_close', close_mine)
else:
out = fire('pup_close', {'all': True, 'everyone': True}, 'jump-list ralph: close-all task'); time.sleep(4)
sess = [s.get('sessionId') for s in (ab(t, 'pup_list_windows', {}).get('sessions') or [])]
check('all windows closed', not sess, f'({out}; sessions={sess})')
fails = [n for n, ok in results if not ok]
print(f'\nJUMPLIST RALPH: {"ALL PASS" if not fails else "FAIL " + ", ".join(fails)} ({len(results) - len(fails)}/{len(results)})')
sys.exit(1 if fails else 0)
if __name__ == '__main__': main()