← Commit history

v1.6.59: bake fresh-install completeness guard (readiness) + launch-error dialog detection & webdeploy auto-clean (fusion_detect) so a mid-stream FusionLauncher never triggers Error Launching Streamed Application

John Lauer ·1018f7e947 ·3mo ago ·parent d103a30
5 files changed +98−13
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.6.58\ No newline at end of file+1.6.59\ 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.58",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.58/adom-bridge-fusion-v1.6.58.zip",-  "sha256": "d528bad4a61f7adab8a15a77eaaa4c197e263ee392cb43b5d627a98943a3c0a9",-  "size": 347717,+  "version": "1.6.59",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.59/adom-bridge-fusion-v1.6.59.zip",+  "sha256": "a4269760b2aa726a06ec346e3dda78b65db5cd5a87e9055b34c4bbb82ad5140c",+  "size": 349140,   "verbPrefixes": [     "fusion_"   ],
bridge.json+2−2
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.6.58",+  "version": "1.6.59",   "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.",@@ -181,4 +181,4 @@       }     ]   }-}+}\ No newline at end of file
fusion_detect.py+74−3
@@ -107,9 +107,52 @@ def _find_fusion_launcher() -> Path | None:      if not candidates:         return None-    # Complete installs (has_ini) win; newest mtime breaks ties.-    candidates.sort(key=lambda c: (c[0], c[1]), reverse=True)-    return candidates[0][2]+    # Only a COMPLETE install (FusionLauncher.exe.ini present) counts as installed. A hash dir with+    # FusionLauncher.exe but NO .ini is a half-streamed/corrupt install - returning it made+    # detect_fusion() report installed:true mid-stream, so a fusion_start fired into it and hit+    # "Error Launching Streamed Application ... .ini is missing or incomplete" (fresh Hyper-V VM,+    # John, 2026-07-14). If nothing is complete, report NOT installed (readiness then surfaces the+    # streamer as installing).+    complete = [c for c in candidates if c[0]]+    if complete:+        complete.sort(key=lambda c: c[1], reverse=True)   # newest complete install wins+        return complete[0][2]+    return None+++_MIN_VALID_INI_BYTES = 512   # the truncated launcher .ini that broke the fresh VM was 412 bytes+++def _clean_incomplete_webdeploy() -> list:+    """Remove webdeploy production hash dirs that are INCOMPLETE: a FusionLauncher.exe present but its+    FusionLauncher.exe.ini missing or truncated (< _MIN_VALID_INI_BYTES). These are the residue of an+    interrupted stream that throws 'Error Launching Streamed Application'. Only ever called AFTER a+    launch failure (we already know something is corrupt), so aggressive cleanup is safe. Returns the+    dir names removed. Never raises."""+    import shutil+    removed = []+    for base in _WEBDEPLOY_BASES:+        if not base.exists():+            continue+        try:+            subdirs = [d for d in base.iterdir() if d.is_dir()]+        except OSError:+            continue+        for subdir in subdirs:+            if not (subdir / "FusionLauncher.exe").exists():+                continue+            ini = subdir / "FusionLauncher.exe.ini"+            try:+                ok = ini.exists() and ini.stat().st_size >= _MIN_VALID_INI_BYTES+            except OSError:+                ok = False+            if not ok:+                try:+                    shutil.rmtree(subdir, ignore_errors=True)+                    removed.append(subdir.name)+                except Exception:+                    pass+    return removed   def fusion_update_in_progress() -> bool:@@ -679,6 +722,34 @@ def ensure_fusion_running(fusion_info: dict, wait_addin: bool = False) -> dict |         if _has_fusion_process():             break     else:+        # LAUNCH FAILED - do NOT return a blind "process not found". READ the dialog that is almost+        # certainly on screen. The classic fresh-box cause is an INCOMPLETE stream: "Error Launching+        # Streamed Application ... FusionLauncher.exe.ini is missing or incomplete" (John, live on a+        # fresh Hyper-V VM 2026-07-14). Detect + dismiss it, CLEAN the corrupt webdeploy dir(s), and+        # tell the caller to re-stream - instead of silently sitting on a broken install.+        launch_err = None+        try:+            from handlers.dialog_classify import classify_launch_dialogs, close_dialog_bg+            for d in classify_launch_dialogs():+                if d.get("category") == "launch_error" or "streamed application" in (d.get("title") or "").lower():+                    launch_err = d+                    close_dialog_bg(d.get("hwnd"))+        except Exception:+            pass+        removed = _clean_incomplete_webdeploy()+        if launch_err or removed:+            return {+                "success": False,+                "error": "Fusion launch failed: INCOMPLETE streamed install (FusionLauncher.exe.ini "+                         "missing or truncated).",+                "errorCode": "fusion_incomplete_install",+                "cleanedWebdeployDirs": removed,+                "dialog": (launch_err or {}).get("title"),+                "_hint": "AUTO-RECOVERED: dismissed the launch-error dialog and removed the corrupt "+                         "webdeploy dir(s). Re-run fusion_install_fusion, then WAIT for fusion_readiness "+                         "to report installing:false (the streamer has exited) BEFORE fusion_start - "+                         "starting mid-stream is what corrupts the install.",+            }         return {             "success": False,             "error": "Fusion 360 launched but process not found after 30s",
server.py+17−3
@@ -1135,9 +1135,23 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict:     # report installed/running/ready:true on a machine where Fusion had been removed     # (found live on a fresh VM 2026-07-05: NO_WEBDEPLOY on disk, yet readiness said     # ready). detect_fusion() is a fast filesystem scan, so pay it every call.-    installed = bool(detect_fusion().get("installed"))-    running = _is_fusion_running() if installed else False-    installing = (not installed) and _installer_running()+    raw_installed = bool(detect_fusion().get("installed"))+    streaming = _installer_running()+    running = _is_fusion_running() if raw_installed else False+    # FRESH-INSTALL COMPLETENESS GUARD (John caught this live 2026-07-14 on a fresh Hyper-V VM):+    # the streamer writes FusionLauncher.exe BEFORE it finishes writing FusionLauncher.exe.ini, so+    # detect_fusion() flips installed:true mid-stream. Starting into that half-written install throws+    # "Error Launching Streamed Application ... FusionLauncher.exe.ini is missing or incomplete" and+    # leaves TWO corrupt webdeploy production dirs. So: while the streamer is running and Fusion is+    # NOT already up (i.e. this is a first install, not a background auto-update of a running Fusion),+    # the install is STILL STREAMING - report installed:false + installing:true so nothing calls+    # fusion_start yet. Only treat it as installed once the streamer has exited.+    if streaming and not running:+        installed = False+        installing = True+    else:+        installed = raw_installed+        installing = (not installed) and streaming      # The add-in only serves once Fusion is PAST sign-in (its HTTP server runs on     # Fusion's main thread, which the sign-in modal blocks). So a responding add-in