← Commit history

demo signin: bridge now OPENS the Autodesk sign-in in the user's native browser IN THE BACKGROUND (via ad_call -> nbrowser_*), reports where it is waiting, and offers all three paths (user finishes / AI foregrounds / AI drives with permission)

John Lauer ·9f5618c07d ·2mo ago ·parent ed89f7a
6 files changed +112−27
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.6.85+1.6.86
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.6.85",+  "version": "1.6.86",   "description": "Drive Autodesk Fusion 360 from the cloud: launch Fusion, electronics board layout, design rules, exports (STEP/IGES/STL/3MF/USDZ/OBJ/DXF/DWG/Gerbers/BOM/CPL), fast APS server-indexed cloud search plus browse/recent/file-info/versions, cloud file download/upload and folder creation, and in-app parametric modeling (fusion_run_modeling_script). Never-charge: APS calls are capped to the free tier.",   "homepage": "https://wiki.adom.inc/adom/adom-desktop-fusion-bridge",   "author": "Adom Inc.",
describe.py+1−1
@@ -37,7 +37,7 @@ _T = [      {"success": "bool", "targets": "list"}, 20, None, False,      {"title": "Fusion needs you", "body": "Please complete the Autodesk 2FA prompt on winvm - everything else is done."}), -    ("fusion_demo", "FIRST-TIME-USER DEMO in one verb - the thing to run when someone new wants to SEE what this bridge does (HD's installer calls this). Takes them from nothing to a real board: launches Fusion, FINISHES the Autodesk sign-in (driven in the user's NATIVE browser via ABE/nbrowser_*, which is already signed into Autodesk - never pup, which is anonymous), sets up APS cloud search (or explains it if declined) and runs a LIVE sample search so they see 2-second results, then opens an electronics PROJECT and walks SCHEMATIC -> 2D BOARD -> 3D BOARD, screenshotting each view to show them. STAGED + RESUMABLE: every call advances as far as it safely can and returns stage/done/narrate/screenshots plus a _hint with the exact next action; keep calling it until done:true. NEVER leave a first-time user parked on the sign-in screen - that is the failure this verb exists to prevent. See the 'fusion-demo' skill.",+    ("fusion_demo", "FIRST-TIME-USER DEMO in one verb - the thing to run when someone new wants to SEE what this bridge does (HD's installer calls this). Takes them from nothing to a real board: launches Fusion, FINISHES the Autodesk sign-in - it OPENS the sign-in in the user's NATIVE browser IN THE BACKGROUND (never interrupting their work) and returns signinWhere/signinProfile/signinSession so you can tell them exactly where it is waiting, then offer three paths: they finish it, you foreground it, or (with their OK) you drive it. Native browser via ABE/nbrowser_* because that profile is already signed into Autodesk - never pup, which is anonymous, sets up APS cloud search (or explains it if declined) and runs a LIVE sample search so they see 2-second results, then opens an electronics PROJECT and walks SCHEMATIC -> 2D BOARD -> 3D BOARD, screenshotting each view to show them. STAGED + RESUMABLE: every call advances as far as it safely can and returns stage/done/narrate/screenshots plus a _hint with the exact next action; keep calling it until done:true. NEVER leave a first-time user parked on the sign-in screen - that is the failure this verb exists to prevent. See the 'fusion-demo' skill.",      {"query": "optional str (design to demo; else picked from APS search)", "stage": "optional str ('skip_aps' to proceed without cloud search)"},      {"stage": "str", "done": "bool", "narrate": "str", "screenshots": "[{label,path}]", "steps": "[str]", "apsSampleSearch": "[{name,project}]"},      240, "fusion_demo", True, {}),
page.json+2−3
@@ -1,6 +1,6 @@ {   "slug": "adom-desktop-fusion-bridge",-  "version": "1.6.85",+  "version": "1.6.86",   "type": "app",   "description": "Installs this bridge's skills into your container so your AI knows how to drive the bridge. The bridge runtime itself is loaded by Adom Desktop from the release zip.",   "dependencies": {@@ -73,6 +73,5 @@     "set up the fusion bridge"   ],   "discovery_pitch": "Use the Adom Desktop Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Desktop. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).",-  "brief": "Drive Autodesk Fusion 360 from the cloud via Adom Desktop: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.",-  "visibility": "public"+  "brief": "Drive Autodesk Fusion 360 from the cloud via Adom Desktop: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling." }\ No newline at end of file
server.py+87−20
@@ -3578,6 +3578,40 @@ def _orchestrate_fetch_optimized_glb(args: dict) -> dict:             "message": "Job %s still %s; re-call fusion_fetch_optimized_glb to finish." % (job_id, res.get("status") or "processing")}  +def _ad_call(verb: str, args: dict, timeout: int = 60) -> dict:+    """Call another AD verb (nbrowser_*, desktop_*) from inside this bridge.++    Prefers the in-process ad_client; falls back to the adom-desktop CLI (which joins the+    relay itself) so this still works on an unattended VM where ad_client is down. Never+    raises - returns {} on failure so callers can degrade to instructing the AI instead."""+    try:+        if ad_client.available():+            r = ad_client.call(verb, args)+            if isinstance(r, dict):+                return r+    except Exception:+        pass+    try:+        import subprocess as _sp, json as _j+        exe = _find_adom_desktop_cli()+        if not exe:+            return {}+        p = _sp.run([exe, verb, _j.dumps(args)], capture_output=True, text=True, timeout=timeout)+        out = (p.stdout or "").strip()+        if out.startswith("{"):+            d = _j.loads(out)+            inner = d.get("output")+            if isinstance(inner, str) and inner.strip().startswith("{"):+                try:+                    return _j.loads(inner)+                except Exception:+                    return d+            return d+    except Exception:+        pass+    return {}++ def _orchestrate_demo(args: dict) -> dict:     """FIRST-TIME-USER DEMO: take a brand-new user from nothing to "wow" in one verb. @@ -3640,26 +3674,59 @@ def _orchestrate_demo(args: dict) -> dict:      # ── 2. SIGN-IN: finish it, do not sit on it ─────────────────────────────────     if rd.get("needsSignin"):-        return _out("signin",-            "Fusion needs your Autodesk sign-in. I'll use your own browser - you're probably "-            "already signed in there, so this should take one click.",-            "FINISH THE SIGN-IN - do NOT leave the user staring at this screen. Use the NATIVE "-            "browser (ABE), never pup: pup is anonymous so Autodesk would demand a full login, "-            "while the user's real Chrome/Edge profile is typically ALREADY signed into Autodesk. "-            "SEQUENCE: (1) nbrowser_readiness - if state!='ready', offer the one-time extension "-            "install (installing-the-extension skill); ABE still exposes nb verbs meanwhile. "-            "(2) Click Fusion's 'Sign In' button in the BACKGROUND via desktop_find_control + "-            "desktop_ui_click (never a coordinate click, never steal focus). That fires the OAuth "-            "URL at the DEFAULT browser. (3) If their default browser is the signed-in profile, it "-            "completes itself - just poll fusion_readiness. If it lands logged-out, open the same "-            "auth URL in the signed-in profile with nbrowser_open_window {profile:'chrome:<their "-            "account>'} (nbrowser_profiles lists them) and click 'Allow'/'Open' there. (4) The "-            "handoff back to Fusion is the 'Autodesk Identity Manager' overlay - tick 'Always allow' "-            "+ 'Open'. (5) NEVER type their password or 2FA: if a secret is demanded, "-            "fusion_notify_owner toasts their machine so THEY type it. Sign-in codes expire in ~2 "-            "min - if it expires, click 'Sign In' again for a fresh one. Poll fusion_readiness until "-            "ready:true, then call fusion_demo again. FULL PLAYBOOK: fusion-autodesk-signin skill.",-            needsSignin=True)+        # DO IT, don't just describe it: open the Autodesk sign-in in the user's OWN browser,+        # in the BACKGROUND so we never yank them out of what they're doing. Then tell the AI+        # exactly WHERE it is waiting and offer the three ways forward.+        nb = _ad_call("nbrowser_readiness", {}, timeout=40)+        nb_data = nb.get("data", nb) if isinstance(nb, dict) else {}+        nb_state = nb_data.get("state")+        opened, where, profile_used = False, "", ""+        if nb_state == "ready":+            profs = _ad_call("nbrowser_profiles", {}, timeout=40)+            pdata = profs.get("data", profs) if isinstance(profs, dict) else {}+            plist = [p for p in (pdata.get("profiles") or []) if isinstance(p, str) and ":" in p]+            profile_used = plist[0] if plist else ""+            oa = {"sessionId": "fusion-signin", "url": "https://accounts.autodesk.com/",+                  "background": True, "thread": "fusion-demo", "purpose": "Autodesk sign-in for Fusion"}+            if profile_used:+                oa["profile"] = profile_used+            r = _ad_call("nbrowser_open_window", oa, timeout=60)+            opened = bool((r.get("data", r) if isinstance(r, dict) else {}).get("sessionId") or r.get("ok") or r.get("success"))+            where = (profile_used.split(":", 1)[0].title() if profile_used else "your browser")+            if profile_used and "@" in profile_used:+                where += " (%s)" % profile_used.split(":", 1)[1]+        steps.append("opened Autodesk sign-in in %s (background)" % (where or "browser") if opened+                     else "could not auto-open the browser sign-in")+        if opened:+            narrate = ("I've opened the Autodesk sign-in for you in %s - it's sitting in the "+                       "background so it won't interrupt what you're doing. Finish it whenever you "+                       "like, or say the word and I'll bring it up front, or I can just complete it "+                       "for you." % where)+        else:+            narrate = ("Fusion needs your Autodesk sign-in. I'd like to use your own browser - you're "+                       "probably already signed in there, so it should take one click.")+        return _out("signin", narrate,+            ("FINISH THE SIGN-IN - never leave a first-time user parked here. " ++             ("A background sign-in tab is ALREADY OPEN in %s (session 'fusion-signin'). " % where+              if opened else "Auto-open failed - open it yourself: nbrowser_open_window "+              "{sessionId:'fusion-signin', url:'https://accounts.autodesk.com/', background:true, "+              "profile:'<chrome:their-account>'}. ") ++             "TELL THE USER WHERE IT IS WAITING and OFFER ALL THREE: (a) they finish it themselves "+             "whenever they want, (b) you foreground that window for them "+             "(nbrowser_switch_window / browser window state), or (c) THEY LET YOU DRIVE IT - ask "+             "first (AskUserQuestion), then click through 'Continue with Google/Apple/Microsoft' "+             "using their warm session. Use the NATIVE browser (ABE), NEVER pup: pup is anonymous so "+             "Autodesk demands a full fresh login, while their real profile is usually already signed "+             "in. NEVER type their password or 2FA - if a secret is demanded, fusion_notify_owner "+             "toasts them to type it. Then click Fusion's own 'Sign In' (the webview exposes no UIA "+             "control, so use an image-space desktop_click on the button, or foreground + click) to "+             "fire the OAuth handoff; accept the 'Autodesk Identity Manager' overlay ('Always allow' "+             "+ 'Open'). Codes expire in ~2 min - if it expires, click Sign In again. Poll "+             "fusion_readiness until ready:true, then call fusion_demo again. Playbook: "+             "fusion-autodesk-signin."),+            needsSignin=True, signinOpened=opened, signinWhere=where,+            signinProfile=profile_used, signinSession="fusion-signin" if opened else None,+            nbState=nb_state)      if not rd.get("ready"):         return _out("waiting",
skills/fusion-demo/SKILL.md+20−1
@@ -45,7 +45,26 @@ Loop: call it, do what `_hint` says, narrate, call it again. Do not improvise a  ## Rule 1: FINISH the sign-in, in the user's NATIVE browser -When `stage:"signin"`:+`fusion_demo` does not just tell you to do this - at `stage:"signin"` it **already opened the+Autodesk sign-in in the user's own browser, in the BACKGROUND**, so their current work is never+interrupted. The response tells you where it landed:++| Field | Meaning |+|---|---|+| `signinOpened` | true if the background tab is up |+| `signinWhere` | human phrasing for WHERE, e.g. "Chrome ([email protected])" - say this out loud |+| `signinProfile` / `signinSession` | the ABE profile + sessionId (`fusion-signin`) to drive it |++**Always tell the user where it is waiting, then offer all three paths:**++1. **They finish it themselves**, whenever they like - it is sitting there, backgrounded.+2. **You bring it to the front** for them (foreground that nb window) if they would rather do it now.+3. **You drive it for them** - ASK first (`AskUserQuestion`), then click through+   "Continue with Google/Apple/Microsoft" on their warm session.++Never silently sit and wait, and never foreground it uninvited.++Then:  - **Use ABE (`nbrowser_*`), never pup.** pup is an anonymous browser, so Autodesk demands a full   login with password + 2FA. The user's real Chrome/Edge profile is **typically already signed into