← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.340\ No newline at end of file+1.9.341\ No newline at end of file
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "fusion360",-  "version": "1.9.340",-  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.340/adom-bridge-fusion-v1.9.340.zip",-  "sha256": "701eba60f60d139c4d1f508a872039cd905ee5ea8b6f85ed1df9674fc9bcfe78",-  "size": 609460,+  "version": "1.9.341",+  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.341/adom-bridge-fusion-v1.9.341.zip",+  "sha256": "0a0121d2c74ce8aad44c3b0cd8ea56864d935f84614ff46a472125dedb8c5ff5",+  "size": 610511,   "verbPrefixes": [     "fusion_"   ],
aps.py+4
@@ -212,6 +212,10 @@ def _launch_url_in_browser(pref: dict, url: str) -> bool:         cmd.append("--new-window")     cmd.append(url)     try:+        # DELIBERATELY LOCAL, not ab's desktop_open_url: that verb takes only+        # {url, browser} and cannot pin --profile-directory, which is the whole point+        # here (the sign-in must land in the profile that owns the Autodesk session).+        # Revisit if desktop_open_url ever grows a profile arg.         subprocess.Popen(cmd, close_fds=True,                          creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))         return True
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.340",+  "version": "1.9.341",   "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.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "fusion-dashboard"-version = "1.9.340"+version = "1.9.341" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.340"+version = "1.9.341" edition = "2021"  [dependencies]
handlers/close_fusion.py+58
@@ -71,8 +71,57 @@ def _close_and_wait(hwnd: int, title: str, timeout: float = 15.0) -> bool:     return False  +def _ab_process_rows(name_substr):+    """Fusion-family process rows via ab's own process_list verb, or None when ab is+    unreachable. VERB-FIRST DOCTRINE (John, 2026-09-01: "aren't those ab verbs you could+    call instead?"): ab already owns host process ops - one implementation, no child+    process to spawn, and the call lands in ab's activity log with this bridge's caller+    identity, where a raw taskkill is invisible. The tasklist/taskkill spawns below survive+    only as the degraded path for when ad_client is down (unattended VM, ab mid-restart).++    NOTE the deliberate exception: fusion_detect's POLLING uses a local Toolhelp32 snapshot+    instead of this verb, and should keep doing so - a poll every few seconds wants+    microseconds in-process, not an HTTP round trip. Verb for OPERATIONS, snapshot for+    POLLS, hidden spawn only as a fallback."""+    try:+        import ad_client+        if not ad_client.available():+            return None+        r = ad_client.call("process_list", {"name": name_substr}, timeout=10)+        if not isinstance(r, dict):+            return None+        rows = r.get("processes")+        if rows is None and isinstance(r.get("output"), str):+            import json as _j+            try:+                rows = (_j.loads(r["output"]) or {}).get("processes")+            except Exception:+                rows = None+        return rows if isinstance(rows, list) else None+    except Exception:+        return None+++def _ab_process_kill(pid, why):+    """Kill one PID through ab's process_kill verb. True on confirmed kill, False when the+    verb was unavailable or refused (caller falls back to a hidden taskkill)."""+    try:+        import ad_client+        if not ad_client.available():+            return False+        r = ad_client.call("process_kill", {"pid": int(pid), "tree": True, "reason": why},+                           timeout=15)+        return bool(isinstance(r, dict) and (r.get("success") or r.get("ok")))+    except Exception:+        return False++ def _fusion_pids() -> list:     """PIDs of every surviving Fusion-family process (tasklist CSV, empty on error)."""+    rows = _ab_process_rows("Fusion")+    if rows is not None:+        return [int(r.get("pid")) for r in rows+                if r.get("pid") and str(r.get("name") or "") in FUSION_PROCESS_NAMES]     pids = []     for name in FUSION_PROCESS_NAMES:         try:@@ -103,6 +152,15 @@ def _force_kill_fusion() -> int:     tried - never with an instruction to open Task Manager.     """     killed = 0+    # Verb-first: enumerate + kill through ab, so the kills are attributed in ab's+    # activity log. Fall through to taskkill for anything the verb path did not confirm.+    _verb_killed = set()+    for r in (_ab_process_rows("Fusion") or []):+        pid = r.get("pid")+        if pid and str(r.get("name") or "") in FUSION_PROCESS_NAMES:+            if _ab_process_kill(pid, "close Fusion: force-kill surviving %s" % r.get("name")):+                killed += 1+                _verb_killed.add(int(pid))     for name in FUSION_PROCESS_NAMES:         try:             result = subprocess.run(
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.340"+DASH_VERSION="1.9.341" 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.340",+  "version": "1.9.341",   "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,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.9.340",+  "version": "1.9.341",   "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",@@ -75,7 +79,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.",@@ -104,9 +111,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+  ]+}