← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.87+1.9.88
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {     "manifest_version": 1,     "name": "fusion360",-    "version": "1.9.87",-    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.100/adom-bridge-fusion-v1.9.87.zip",-    "sha256": "d1cf665859da8d1dacb67ac60d2e9b7d36dca5cea945197473025448a5188a48",-    "size": 425140,+    "version": "1.9.88",+    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.101/adom-bridge-fusion-v1.9.88.zip",+    "sha256": "6099e2b6d130fe7a6edca8ed4c7749ae479196bc8bcc13956e0ee303897a38af",+    "size": 426021,     "verbPrefixes": [         "fusion_"     ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.87",+  "version": "1.9.88",   "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.100"+version = "1.9.101" edition = "2021"  [dependencies]
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.100"+DASH_VERSION="1.9.101" 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.100",+  "version": "1.9.101",   "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+11−8
@@ -1,16 +1,20 @@ {   "slug": "fusion-bridge",-  "version": "1.9.100",+  "version": "1.9.101",   "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": {-    "adom/adom-desktop": "*"+    "adom/adom-desktop": "^1.9.0"   },   "scripts": {     "install": "./install.sh",     "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+52−2
@@ -535,6 +535,26 @@ def _handle_new_electronics_from_eagle(args: dict) -> dict:     }  ++def _addin_round_trip_ready(tries: int = 2, gap: float = 1.5) -> bool:+    """Can the add-in actually EXECUTE a command, twice in a row?++    The distinction that issue #36 turned on: a listening socket proves the add-in's HTTP+    thread is up, not that Fusion's main thread will run our command. get_app_state is the+    cheapest real round-trip, so it is the honest readiness test.+    """+    import time as _t+    for i in range(tries):+        try:+            if not _proxy_to_addin("get_app_state", {}, timeout=6).get("success"):+                return False+        except Exception:+            return False+        if i + 1 < tries:+            _t.sleep(gap)+    return True++ def _handle_launch(fusion_info: dict, args: dict) -> dict:     """Launch Fusion 360 and optionally wait for the AdomBridge add-in."""     # LIVE detect (not the stale bridge-start fusion_info snapshot) so a launch right@@ -648,7 +668,17 @@ def _handle_launch(fusion_info: dict, args: dict) -> dict:             _t.sleep(1.0)             continue         # No seat dialog -> is the add-in actually serving? (ground truth for drivable)-        if _probe_addin(timeout=1.5):+        #+        # ⚠️ A PORT THAT ANSWERS IS NOT AN ADD-IN THAT CAN WORK (page issue #36, found by+        # ralph-testing the web-control page): `_probe_addin` proves a socket replied, and+        # this verb's own hint promises it "blocks until the add-in is ready". A caller+        # reasonably took that at face value, fired the next verb, and got "add-in not+        # responding" - three surface buttons failed in that window, and minutes later the+        # identical calls worked. So require a REAL command round-trip (get_app_state, the+        # cheapest thing the add-in actually executes on its main thread), and require it+        # TWICE: the first success can land while the add-in is still finishing startup,+        # and one more tick is far cheaper than handing the caller a false ready.+        if _probe_addin(timeout=1.5) and _addin_round_trip_ready():             addin_ok = True             # Give the human a window worth looking at (background, no activation).             try:@@ -1570,6 +1600,26 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict:                 "fusion_install_fusion (no shell approval needed) - it streams the free trial and "                 "this verb reports installing:true until done.") +    # READY MUST MEAN DRIVABLE (page issue #36). `addin_ok` above comes from an HTTP GET to+    # the add-in's own web server, which is served by its HTTP thread - it is up well before+    # Fusion's MAIN thread will execute a command. Callers poll this verb precisely to learn+    # "can I fire my next verb now", so confirm with one real round-trip before saying yes.+    # Cheap when the main thread is free (~50ms); when it is not, failing here is exactly the+    # answer the caller needed.+    if ready:+        try:+            if not _proxy_to_addin("get_app_state", {}, timeout=4).get("success"):+                ready = False+                result_note = ("the add-in's HTTP server is up but its main thread did not "+                               "execute a test command yet - not drivable this second")+            else:+                result_note = None+        except Exception:+            ready = False+            result_note = "the add-in did not complete a test command round-trip"+    else:+        result_note = None+     result = {         "success": True,         "hostApp": "Fusion 360",@@ -1579,7 +1629,7 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict:         "ready": ready,         "bridgeVersion": BRIDGE_VERSION,         **stale_info,-        "_hint": hint,+        "_hint": (hint + " NOTE: " + result_note) if result_note else hint,     }     # MEASURED PROGRESS WHILE LAUNCHING (issue #32): the poll verb must render the same     # bar the launch verb started, or the caller is back to a spinner with no end.