← Commit history

v1.6.61: fusion_notify_owner SELF-DELIVERS via adom-desktop --target all when the in-process ad_client is unavailable (headless VM) - the toast actually leaves the box and reaches the user's real machine, no AI relay to forget

John Lauer ·83ef57b276 ·3mo ago ·parent ad0cbee
4 files changed +66−9
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.6.60\ No newline at end of file+1.6.61\ No newline at end of file
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "fusion360",-  "version": "1.6.60",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.60/adom-bridge-fusion-v1.6.60.zip",-  "sha256": "03b4b9b136925eb216517e1051d0fc4fb378fdc5a32c0db7820657fe3d22599f",-  "size": 355794,+  "version": "1.6.61",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.61/adom-bridge-fusion-v1.6.61.zip",+  "sha256": "a867c2099758e3b2363c65c07ede5acdd9a837d39939d96cd74fe0c5306e6cc2",+  "size": 357063,   "verbPrefixes": [     "fusion_"   ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.6.60",+  "version": "1.6.61",   "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.",
server.py+60−3
@@ -198,6 +198,47 @@ def _resolve_seat_dialog(max_clicks: int = 8) -> dict:     return {"sawDialog": saw, "clicks": clicks, "verified": verified}  +def _find_adom_desktop_cli() -> str | None:+    """Locate the adom-desktop CLI/exe so the bridge can drive AD's relay directly (used when the+    in-process ad_client is unavailable, e.g. on a headless VM). Checks the per-user Windows install+    dir, then PATH, then the common Linux/dev locations. Returns a path or None."""+    import shutil+    la = os.environ.get("LOCALAPPDATA", "")+    candidates = [+        os.path.join(la, "Adom Desktop", "adom-desktop.exe") if la else None,+        os.path.join(la, "Programs", "Adom Desktop", "adom-desktop.exe") if la else None,+    ]+    for c in candidates:+        if c and os.path.exists(c):+            return c+    return shutil.which("adom-desktop") or shutil.which("adom-desktop.exe")+++def _cli_notify_all(title: str, body: str, level: str) -> dict:+    """Deliver a toast to EVERY connected desktop via `adom-desktop --target all notify_user`. This is+    the reliable cross-AD path when the bridge's in-process ad_client is down (headless VM): the CLI+    joins the relay itself and `--target all` reaches the user's real machine (not just this VM).+    Best-effort + never raises. Returns {delivered, targets, error}. (John 2026-07-14: the notify MUST+    actually leave the box - a returned-but-unsent payload is the bug, not a feature.)"""+    import subprocess as _sp+    exe = _find_adom_desktop_cli()+    if not exe:+        return {"delivered": False, "error": "adom-desktop CLI not found"}+    payload = json.dumps({"title": title, "body": body, "level": level})+    try:+        r = _sp.run([exe, "--target", "all", "notify_user", payload],+                    capture_output=True, text=True, timeout=30,+                    creationflags=getattr(_sp, "CREATE_NO_WINDOW", 0))+        out = (r.stdout or "") + (r.stderr or "")+        # AD's CLI returns {status:'ok', action:'displayed'} per target; treat a zero exit or a+        # 'displayed'/'ok' in the output as delivered. The Windows exe may print nothing yet still+        # deliver, so a clean exit code is accepted too.+        delivered = (r.returncode == 0) or ("displayed" in out) or ('"status": "ok"' in out) or ("status':'ok" in out)+        return {"delivered": bool(delivered), "targets": "all", "error": None if delivered else out[:200]}+    except Exception as e:+        return {"delivered": False, "error": str(e)[:200]}++ def _handle_notify_owner(args: dict) -> dict:     """LAST-RESORT: toast the user's MAIN desktop (cross-AD) to ask for a human step. @@ -210,12 +251,28 @@ def _handle_notify_owner(args: dict) -> dict:     body = args.get("body") or args.get("message") or "A human step is required to continue."     level = args.get("level") or "warning"     if not ad_client.available():+        # ad_client is the bridge's IN-PROCESS AD API - it is routinely UNAVAILABLE when this bridge+        # runs on an unattended Hyper-V VM (found live 2026-07-14: the toast silently never reached the+        # user, and the AI "forgot" to relay it - exactly the failure John demanded we engineer out).+        # SELF-DELIVER via the adom-desktop CLI, which connects to the relay independently: `--target+        # all` fans the toast out to EVERY connected desktop (the VM + the user's real machines), so it+        # reliably lands on the computer the user is actually AT. No AI relay step to forget.+        cli = _cli_notify_all(title, body, level)+        if cli.get("delivered"):+            return {"success": True, "via": "cli:--target all", "targets": cli.get("targets"),+                    "_hint": ("Toast fanned out to ALL connected desktops (the user's main machine "+                              "included) via the adom-desktop CLI, because the bridge's in-process AD "+                              "API was unavailable (this VM). WAIT and poll fusion_readiness; do not "+                              "re-toast within a few minutes. Only notify when the box is ACTUALLY "+                              "ready for the user to act - do not toast for a step you can do yourself.")}+        # CLI fallback also failed - return the payload so the AI can relay as the true last resort.         return {             "success": False,-            "error": "AD direct API unavailable - cannot send a toast from the bridge.",+            "error": "AD in-process API unavailable AND the adom-desktop CLI fallback failed: "+                     + str(cli.get("error"))[:200],             "notifyUser": {"title": title, "body": body, "level": level},-            "_hint": ("Relay the included notifyUser payload yourself via AD's notify_user verb "-                      "(add a cross-AD `target` if this bridge runs on a VM and the user is elsewhere)."),+            "_hint": ("Relay the notifyUser payload yourself: `adom-desktop --target all notify_user "+                      "{title, body, level}` (or `--target <the user's host>`)."),         }     peers = []     try: