← Commit history

Upload 1 file(s)

John Lauer ·6615aa70e9 ·2mo ago ·parent 7822f48
1 file changed +56−12
server.py+56−12
@@ -3759,18 +3759,39 @@ def _fusion_window_hwnd(): # tree so page buttons/fields ARE reachable by accessible name. So: ALWAYS try background UIA # first and only fall back to a foreground click for things with no UIA node (Fusion's Sign In # button is a webview with none - that is the one documented exception).-def _click_background_first(hwnd, name=None, shot_id=None, x=None, y=None) -> dict:-    """Click preferring the BACKGROUND UIA path. Returns {clicked, via, why}."""+def _announce_foreground(reason: str) -> bool:+    """ALWAYS tell the user WHY, right before we steal their foreground.++    John, 2026-07-22: "anytime you bring something to the foreground, you should show an ad notify+    for 2 seconds telling the user why... or an ad caption." A caption is the gentler of the two+    (no toast stack, auto-clears), so that is what we use. Best-effort; never blocks the action.+    """+    try:+        _ad_call("desktop_caption",+                 {"text": "Adom: %s" % reason, "id": "fusion-bridge-foreground",+                  "expiresInMs": 2500}, timeout=15)+        return True+    except Exception:+        return False+++def _click_background_first(hwnd, name=None, shot_id=None, x=None, y=None, reason=None) -> dict:+    """Click preferring the BACKGROUND UIA path (no focus steal, no cursor move). Only falls back+    to SendInput - which DOES foreground - when there is no UIA node, and in that case announces+    WHY to the user first. Returns {clicked, via, why, announced}."""     if name:         r = _unwrap(_ad_call("desktop_ui_click", {"hwnd": int(hwnd), "name": name}, timeout=30))         if r.get("ok") or r.get("clicked") or r.get("invoked"):-            return {"clicked": True, "via": "uia_background", "why": "UIA Invoke on %r" % name}+            return {"clicked": True, "via": "uia_background", "announced": False,+                    "why": "UIA Invoke on %r (background, no focus steal)" % name}     if shot_id is not None and x is not None and y is not None:+        announced = _announce_foreground(reason or "clicking %s for you" % (name or "a button"))         _ad_call("desktop_click", {"space": "image", "shotId": shot_id,                                    "x": int(x), "y": int(y), "hwnd": int(hwnd)}, timeout=30)-        return {"clicked": True, "via": "foreground_click",-                "why": "no UIA node; fell back to SendInput (this one DOES foreground)"}-    return {"clicked": False, "via": None, "why": "no UIA node and no shot coords given"}+        return {"clicked": True, "via": "foreground_click", "announced": announced,+                "why": "no UIA node; used SendInput (DOES foreground - user was told why)"}+    return {"clicked": False, "via": None, "announced": False,+            "why": "no UIA node and no shot coords given"}   # ── HUMAN WALLS: notify the user, and tell the AI it can drive it ────────────────────────────@@ -3778,19 +3799,38 @@ def _click_background_first(hwnd, name=None, shot_id=None, x=None, y=None) -> di # "suggest to the ai via hints that you can drive the thing on your own cuz the adom user wants # you to do everything automatically if you can". So on every wall we do BOTH: toast the human, # and hand the AI an exact, mechanical way to clear it without them.-def _detect_signin_wall() -> dict:+def _autodesk_window_hwnds() -> set:+    """hwnds of every Autodesk-ish BROWSER window right now (used to tell NEW from STALE)."""+    wins = _unwrap(_ad_call("desktop_list_windows", {}, timeout=40)).get("windows") or []+    out = set()+    for w in wins:+        tl = str(w.get("title", "")).lower()+        if "autodesk" in tl and "fusion" not in tl:+            out.add(w.get("hwnd"))+    return out+++def _detect_signin_wall(ignore_hwnds=None) -> dict:     """Classify what the Autodesk sign-in is waiting on, from BROWSER WINDOW TITLES.-    Title-based on purpose: cheap, needs no OCR, and never touches/foregrounds the window."""+    Title-based on purpose: cheap, needs no OCR, and never touches/foregrounds the window.++    ignore_hwnds = windows that already existed BEFORE we opened this sign-in. Without it a dead+    'Sign-in request expired' / stale '2-step verification' tab from an earlier attempt gets+    reported as the live wall and we toast the user about nothing (seen live 2026-07-22).+    """+    ignore = set(ignore_hwnds or ())     wins = _unwrap(_ad_call("desktop_list_windows", {}, timeout=40)).get("windows") or []     best = {}     for w in wins:+        if w.get("hwnd") in ignore:+            continue         t = str(w.get("title", "")); tl = t.lower()         if "2-step verification" in tl or "confirm sign-in" in tl:             return {"wall": "twofactor_email_code", "hwnd": w.get("hwnd"), "title": t}-        if "sign in - autodesk" in tl and not best:-            best = {"wall": "credentials", "hwnd": w.get("hwnd"), "title": t}         if "identity manager" in tl:             best = {"wall": "protocol_handoff", "hwnd": w.get("hwnd"), "title": t}+        elif "sign in - autodesk" in tl and not best:+            best = {"wall": "credentials", "hwnd": w.get("hwnd"), "title": t}     return best  @@ -3886,7 +3926,8 @@ def _fusion_click_signin() -> dict:     # node, so this almost always falls through to the image-space click - that is the documented     # exception to the never-foreground rule, not a licence to foreground elsewhere.     r = _click_background_first(hwnd, name="Sign In", shot_id=sid,-                                x=int(w * 0.5), y=int(h * 0.57))+                                x=int(w * 0.5), y=int(h * 0.57),+                                reason="signing Fusion in for you (clicking Sign In)")     return {"clicked": r.get("clicked"),             "why": "%s (image %dx%d)" % (r.get("why"), w, h),             "via": r.get("via")}@@ -4109,6 +4150,9 @@ def _orchestrate_signin(args: dict) -> dict:     tdesc = next((_describe_profile(c) for c in choices if c.get("profile") == target), target)     import time as _tt     sess = "fusion-signin-%d" % int(_tt.time())   # unique per attempt (a reused id can be refused)+    # snapshot the Autodesk windows that already exist, so a STALE expired tab from an earlier+    # attempt is never mistaken for this attempt's wall+    pre_hwnds = _autodesk_window_hwnds()     _open_args = {"sessionId": sess, "url": auth["url"], "background": True,                   "profile": target, "thread": "fusion-signin",                   "purpose": "Fusion Autodesk sign-in (correct profile)"}@@ -4134,7 +4178,7 @@ def _orchestrate_signin(args: dict) -> dict:      # Did the reopened sign-in immediately hit a HUMAN WALL (2FA, credentials, protocol dialog)?     # If so, toast the user AND tell the AI how to clear it for them.-    wall = _detect_signin_wall() if opened else {}+    wall = _detect_signin_wall(ignore_hwnds=pre_hwnds) if opened else {}     wall_kind = wall.get("wall")     notified = False     if wall_kind: