master
John Lauer Gates run beside other threads' windows (own profile, untouched); smoke expects no owner chip by default f38b7d9 29d ago
#!/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()