← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.89+1.9.90
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {     "manifest_version": 1,     "name": "fusion360",-    "version": "1.9.89",-    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.104/adom-bridge-fusion-v1.9.89.zip",-    "sha256": "26036f69bd05b2f09c238fa337230ff96fc5cf230c31cf6a928f548abf60cfc7",-    "size": 426798,+    "version": "1.9.90",+    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.105/adom-bridge-fusion-v1.9.90.zip",+    "sha256": "f8bb0b9e3d0392f795cf7d86d6895f821f7db1a09f4797bfb9e5c8a6368f29a1",+    "size": 428521,     "verbPrefixes": [         "fusion_"     ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.89",+  "version": "1.9.90",   "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/fusion-bridge",   "author": "Adom Inc.",
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.104"+version = "1.9.105" edition = "2021"  [dependencies]
handlers/fusion_ui.py+58
@@ -953,3 +953,61 @@ def ensure_reasonable_window(fraction: float = 0.92, hwnd=None) -> dict:                 "why": "grew to %d%% of the work area without activating it" % int(fraction * 100)}     except Exception as e:         return {"sized": False, "why": "error: %s" % str(e)[:120]}+++def is_fusion_hung() -> dict:+    """Is the Fusion HOST PROCESS hung, as opposed to the add-in being down?++    They need OPPOSITE responses from a caller (page issue #38): an add-in that is down+    can be reloaded by restarting Fusion politely; a process that has stopped pumping its+    message queue cannot be reached over IPC at all, so nothing but a forced restart helps.+    Reporting the second as the first sent a reader hunting a blocking dialog that did not+    exist.++    Two signals, cheap and independent:+      - IsHungAppWindow: the OS's own answer, which is what the shell uses.+      - a top-level window of class "Ghost": the placeholder Windows substitutes for a+        non-pumping window, so its presence is corroboration that survives a racy probe.+    Returns {hung, signals[], hwnd}. Never raises - a detector that can throw is worse+    than no detector.+    """+    out = {"hung": False, "signals": [], "hwnd": None}+    try:+        hwnd = _find_fusion_hwnd()+        out["hwnd"] = hwnd or None+        if hwnd:+            try:+                if bool(user32.IsHungAppWindow(hwnd)):+                    out["hung"] = True+                    out["signals"].append("IsHungAppWindow")+            except Exception:+                pass+        # Ghost-class corroboration: enumerate top-level windows and look at the class.+        ghosts = []+        WNDENUMPROC_G = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.wintypes.HWND,+                                           ctypes.wintypes.LPARAM)++        def cb(h, _l):+            try:+                buf = ctypes.create_unicode_buffer(64)+                user32.GetClassNameW(h, buf, 64)+                if buf.value == "Ghost" and user32.IsWindowVisible(h):+                    t = ctypes.create_unicode_buffer(256)+                    user32.GetWindowTextW(h, t, 256)+                    if "Fusion" in (t.value or ""):+                        ghosts.append(h)+            except Exception:+                pass+            return True++        c = WNDENUMPROC_G(cb)+        _callbacks.append(c)+        user32.EnumWindows(c, 0)+        _callbacks.remove(c)+        if ghosts:+            out["hung"] = True+            out["signals"].append("GhostWindow")+            out["ghostHwnds"] = ghosts+    except Exception:+        pass+    return out
install.sh+1−1
@@ -21,7 +21,7 @@ echo "Installed fusion-bridge skills: $(ls "$PKG_DIR/skills" 2>/dev/null | tr '\ # binary (the three-artifact pattern: bridge zip -> ab, THIS pkg -> container skills + # this download, page media -> wiki). Per the wiki's PKG_VS_RELEASE policy, binaries are # platform-tagged RELEASE assets, never tarball content - the installer fetches them.-DASH_VERSION="1.9.104"+DASH_VERSION="1.9.105" DASH_URL="https://wiki.adom.inc/download/adom/fusion-bridge/${DASH_VERSION}/fusion-dashboard" DEST="" if [ -w /usr/local/bin ]; then DEST=/usr/local/bin
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.9.104",+  "version": "1.9.105",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {
page.json+10−7
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.9.104",+  "version": "1.9.105",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {@@ -11,6 +11,10 @@     "uninstall": "./uninstall.sh"   },   "tags": [+    "ab",+    "bridge",+    "fusion",+    "desktop-app",     "fusion360",     "autodesk",     "cad",@@ -70,7 +74,10 @@     "build a basic parts library",     "add a 3d model to a component",     "make a fusion 360 library with 3d",-    "set up the fusion bridge"+    "set up the fusion bridge",+    "fusion dashboard",+    "fusion status page",+    "open the fusion launch page"   ],   "discovery_pitch": "Use the Adom Bridge 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 Bridge. 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 Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.",@@ -99,9 +106,5 @@       "label": "Part volume",       "prompt": "How much material does each printed part in this assembly use?"     }-  ],-  "author": {-    "name": "John Lauer",-    "email": "[email protected]"-  }+  ] }\ No newline at end of file
server.py+66−2
@@ -2733,14 +2733,50 @@ def _diagnose_addin_timeout(command: str, original_result: dict) -> dict:             },         } -    # Health probe failed — add-in HTTP server is down+    # Health probe failed. TWO DIFFERENT FAULTS LIVE HERE and they need opposite+    # responses (page issue #38): the add-in being down is recoverable over IPC, a HUNG+    # HOST PROCESS is not. Reporting the second as the first sent a reader hunting a+    # blocking dialog that did not exist.+    try:+        from handlers.fusion_ui import is_fusion_hung+        _hung = is_fusion_hung()+    except Exception:+        _hung = {"hung": False, "signals": []}+    if _hung.get("hung"):+        # DOCTRINE: this is mechanically fixable and the user should never have to do it.+        # A hung process will not close politely, so go straight to the force path.+        _healed = False+        try:+            dispatch_command("kill", {})+            _t3 = __import__("time"); _t3.sleep(6)+            dispatch_command("start", {})+            _healed = True+        except Exception:+            pass+        return {+            "success": False,+            "error": "Fusion 360 itself is NOT RESPONDING (the host process stopped pumping "+                     "its message queue), so the add-in cannot answer.",+            "errorCode": "fusion_host_hung",+            "hungSignals": _hung.get("signals"),+            "ghostWindow": bool("GhostWindow" in (_hung.get("signals") or [])),+            "restartAttempted": _healed,+            "_hint": ("This is NOT a blocking dialog and NOT an add-in crash - do not go "+                      "looking for a modal, there is not one. Nothing reaches Fusion over IPC "+                      "while it is hung; only a restart clears it. "+                      + ("The bridge already force-restarted it (kill + start) - poll "+                         "fusion_readiness until ready:true." if _healed else+                         "Restart it with fusion_kill then fusion_start.")),+        }     return {         "success": False,         "error": "AdomBridge add-in not responding (it may have crashed).",         "errorCode": "fusion_addin_not_responding",+        "hostResponding": True,         "_hint": "Fix it YOURSELF - never ask the user: restart Fusion via fusion_stop + "                  "fusion_start (the bridge installs the add-in to ALL Fusion add-in dirs incl. "-                 "%APPDATA%\\Autodesk\\FusionAddins, and runOnStartup reloads it).",+                 "%APPDATA%\\Autodesk\\FusionAddins, and runOnStartup reloads it). The host "+                 "process IS responding - this is the add-in only, so no dialog is implied.",     }  @@ -8619,10 +8655,38 @@ def _dispatch_command_inner(command: str, args: dict, caller_identity: dict = No         if addin_busy and addin_busy.get("busy"):             pass  # Fall through to busy gate         elif not wait_for_addin(timeout=5):+            # Hung HOST vs down ADD-IN (page issue #38) - opposite remedies, so say which.+            try:+                from handlers.fusion_ui import is_fusion_hung+                _h = is_fusion_hung()+            except Exception:+                _h = {"hung": False}+            if _h.get("hung"):+                _ok = False+                try:+                    dispatch_command("kill", {})+                    _time.sleep(6)+                    dispatch_command("start", {})+                    _ok = True+                except Exception:+                    pass+                return {+                    "success": False,+                    "error": "Fusion 360 itself is NOT RESPONDING (hung host process); the "+                             "add-in cannot answer while it is.",+                    "errorCode": "fusion_host_hung",+                    "hungSignals": _h.get("signals"),+                    "restartAttempted": _ok,+                    "_hint": ("Do NOT hunt for a blocking dialog - there is not one. Nothing "+                              "reaches Fusion over IPC while the host is hung. "+                              + ("The bridge force-restarted it; poll fusion_readiness."+                                 if _ok else "Use fusion_kill then fusion_start.")),+                }             return {                 "success": False,                 "error": "Fusion 360 is running but AdomBridge add-in not responding.",                 "errorCode": "fusion_addin_not_responding",+                "hostResponding": True,                 "_hint": "The add-in isn't responding. Fix it YOURSELF - never ask the user: the bridge auto-installs the add-in to ALL Fusion add-in dirs (2025+ Fusion scans %APPDATA%/Autodesk/FusionAddins; the legacy API/AddIns dirs are silently ignored - issue #63), so restart Fusion via fusion_stop + fusion_start and it loads (runOnStartup). Verify with fusion_addin_status.",             }