← Commit history

Upload 1 file(s)

John Lauer ·1720e22882 ·2mo ago ·parent c5b9337
1 file changed +28−5
aps.py+28−5
@@ -287,16 +287,25 @@ def _open_os_window_extension_free(url: str, profile: str = None) -> dict:             profile = None     a = {"sessionId": "aps-signin-%d" % int(time.time()),          "thread": "fusion-aps-signin",-         "purpose": "Autodesk APS consent (extension-free)",+         "purpose": "Autodesk APS consent",          "url": url, "background": True}     if profile:         a["profile"] = profile-    r = _ad("nbrowser_open_os_window", a, timeout=60)+    # PREFER the extension window: nbrowser_open_window makes the tab AGENT-OWNED, and CDP only+    # drives tabs it owns (nbrowser_* refuses `not_owned` with no override). Owning it is what+    # lets us CLICK 'Allow' for the user instead of leaving a consent page sitting there - the+    # same pattern that finally completed the Fusion sign-in. Fall back to the extension-free+    # OS window when there is no extension (issue #12: it must still work with none installed).+    r = _ad("nbrowser_open_window", a, timeout=60)+    owned = isinstance(r, dict) and not r.get("_adCallError") and (r.get("ok") or r.get("sessionId"))+    if not owned:+        r = _ad("nbrowser_open_os_window", a, timeout=60)     if not isinstance(r, dict) or r.get("_adCallError"):         return {"launched": False, "profile": profile,                 "error": (r or {}).get("_adCallError") or "nbrowser_open_os_window unavailable"}-    launched = bool(r.get("ok") or r.get("hwnd") or "Launch fired" in str(r.get("_hint", "")))-    return {"launched": launched,+    launched = bool(r.get("ok") or r.get("hwnd") or r.get("sessionId")+                    or "Launch fired" in str(r.get("_hint", "")))+    return {"launched": launched, "agentOwned": bool(owned),             "where": r.get("displayName") or r.get("profile") or profile,             "profile": r.get("profile") or profile,             "hwnd": r.get("hwnd"),@@ -725,13 +734,27 @@ def handle_status(args: dict) -> dict:     tok = _load_tokens()     has_at = bool(tok.get("access_token"))     live = has_at and int(tok.get("expires_at", 0)) > int(time.time())+    # signedIn MUST mean "I can actually call APS right now", not "a token file exists".+    # It used to be bool(has_at), so status happily reported signedIn:true while every search+    # died with APS token_expired - and fusion_readiness repeated that lie to the user+    # (caught live 2026-07-22). _valid_access_token() refreshes when possible, so this is the+    # honest signal: usable == live now, or refreshable now.+    usable = bool(live)+    if has_at and not live:+        try:+            usable = bool(_valid_access_token())+        except Exception:+            usable = False+    refresh_failed = bool(has_at and not usable)     with _signin_lock:         pending = bool(_signin_state and not _signin_state["done"]                        and time.time() - _signin_state["startedAt"] < 180)     usage = _read_usage()     data = {         "configured": bool(client_id),-        "signedIn": bool(has_at),+        "signedIn": bool(usable),          # usable NOW (refreshed if needed), not merely present+        "hasStoredToken": bool(has_at),    # a token file exists (may be dead)+        "refreshFailed": refresh_failed,   # stored token is dead and could not be refreshed         "tokenLive": bool(live),         "tokenExpiresAt": tok.get("expires_at"),         "scope": tok.get("scope", SCOPES),