← Commit history

#133: approval before the import's dialogs

Colby Knox ·73409a7740 ·12d ago ·parent 8092927
2 files changed +38−1
CHANGELOG.md+9
@@ -3,6 +3,15 @@ All notable changes to the Adom Bridge Fusion 360 bridge, newest first. Canonical: https://wiki.adom.inc/adom/fusion-bridge ยท GitHub backup: `adom-inc/fusion-bridge` +## 1.13.43 (2026-10-30)++- Issue #133, the cause on Andy's PC: ab gates UI Automation and keystrokes behind its shell approval even for a+  bridge, so with no grant every way of filling the Open dialog was refused (needs_approval, reproduced on the test+  laptop with approval off). The import now checks approval before any dialog opens: without it, it asks ab (the+  prompt shows in Adom Bridge or Hydrogen), the start reply carries `needsApproval` and `tellUser`, the run's status+  reads `waiting_for_approval`, and it waits up to five minutes for the click. With no click it stops with+  needs_approval and no dialog opened.+ ## 1.13.42 (2026-10-30)  Issue #133, after Andy's run on 1.13.41 failed on his PC ("the File name box could not be set"):
rust/bridge/src/orchestrations.rs+29−1
@@ -1284,7 +1284,10 @@ pub fn new_electronics_from_eagle(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, V             v["stage"] = json!(if r["success"].as_bool() == Some(true) { "done" } else { "failed" }); v["finishedAt"] = json!(now_f()); v["result"] = r;             eagle_run_write(&sd, &rid, &v);         });-        return crate::display::annotate(json!({"success": true, "started": true, "runId": run_id, "statusVerb": "fusion_eagle_import_status", "partsExpected": expected,+        let needs_ok = fusion_core::ab::Loopback::from_env().and_then(|ab| { use fusion_core::ab::AbClient; ab.call("status", json!({}), &ctx.caller, 10).ok() })+            .map(|s| { let x = &s["permissions"]["shell"]; let x = if x.is_object() { x } else { &s["data"]["permissions"]["shell"] }; x["autoApproved"].as_bool() == Some(false) }).unwrap_or(false);+        return crate::display::annotate(json!({"success": true, "started": true, "runId": run_id, "statusVerb": "fusion_eagle_import_status", "partsExpected": expected, "needsApproval": needs_ok,+            "tellUser": if needs_ok { json!("Adom Bridge is asking for your OK to fill in Fusion's Open dialog for the import: click Approve in the Adom Bridge window, on its notification, or in Hydrogen.") } else { Value::Null },             "dialogsDriven": "Fusion's own Open dialogs (sch, then brd), driven in the background by accessible name; Autodesk offers no API for a sch+brd import",             "_hint": format!("Import running in the background (it outlives the relay window). Poll fusion_eagle_import_status {{runId:\"{run_id}\"}} until stage is done or failed; fusion_get_app_state keeps answering meanwhile. The record reports documentName, partsExpected ({expected} elements in the .brd) and partsPlaced from the live board; success needs parts on the board.")}));     }@@ -1326,6 +1329,31 @@ pub fn new_electronics_from_eagle(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, V     };     let find_open_dialog = || -> Option<i64> { ab.call("desktop_list_windows", json!({}), &ctx.caller, 10).ok().and_then(|v| v["data"]["windows"].as_array().or_else(|| v["windows"].as_array()).cloned()).unwrap_or_default().iter().find(|w| w["title"].as_str().unwrap_or("").trim().to_lowercase() == "open").and_then(|w| w["hwnd"].as_i64()) };     let (sch_base, brd_base) = (sch.rsplit('/').next().unwrap_or("").to_string(), brd.rsplit('/').next().unwrap_or("").to_string());+    // The Open dialogs are filled with UI Automation and keystrokes, which ab gates behind its shell+    // approval even for a bridge (#133: on a PC with no grant every fill was refused, needs_approval).+    // Ask BEFORE any dialog opens: the prompt shows in Adom Bridge (or Hydrogen), and the run waits up to+    // five minutes for the click; no grant, no dialog.+    let approved = |st: &Value| { let s = &st["permissions"]["shell"]; let s = if s.is_object() { s } else { &st["data"]["permissions"]["shell"] }; s["autoApproved"].as_bool() == Some(true) };+    if !ab.call("status", json!({}), &ctx.caller, 10).map(|s| approved(&s)).unwrap_or(true) {+        let tell = "Adom Bridge needs your OK to fill in Fusion's Open dialog for the EAGLE import: click Approve (1 hr, 24 hr or Auto) in the Adom Bridge window, on its notification, or in Hydrogen.";+        if let Some(rid) = args["_runId"].as_str() {+            let mut v = std::fs::read_to_string(eagle_run_path(&ctx.state_dir, rid)).ok().and_then(|t| serde_json::from_str::<Value>(&t).ok()).unwrap_or(json!({}));+            v["stage"] = json!("waiting_for_approval"); v["tellUser"] = json!(tell);+            eagle_run_write(&ctx.state_dir, rid, &v);+        }+        let g = ab.call("request_shell_approval", json!({"wait": true, "timeoutSec": 300, "reason": "Fill in Fusion's Open dialog with the EAGLE .sch and .brd paths for the design import"}), &ctx.caller, 320);+        let granted = g.as_ref().map(|v| v["granted"].as_bool() == Some(true) || v["data"]["granted"].as_bool() == Some(true)).unwrap_or(false);+        if let Some(rid) = args["_runId"].as_str() {+            let mut v = std::fs::read_to_string(eagle_run_path(&ctx.state_dir, rid)).ok().and_then(|t| serde_json::from_str::<Value>(&t).ok()).unwrap_or(json!({}));+            v["stage"] = json!("started"); v["approval"] = json!({"granted": granted});+            eagle_run_write(&ctx.state_dir, rid, &v);+        }+        if !granted {+            return json!({"success": false, "errorCode": "needs_approval", "imported": [], "dialogOpened": false,+                "error": "Not started: the import fills Fusion's Open dialog, which needs Adom Bridge's approval, and no approval was given (the request waited five minutes). No dialog was opened.",+                "tellUser": tell, "_hint": "Ask the user to click Approve in Adom Bridge (or Hydrogen), then run the import again. With approval on (Auto mode) the import needs no prompt."});+        }+    }     let mut stale_cancelled = false;     if let Some(h) = find_open_dialog() { stale_cancelled = crate::small_verbs::close_native_dialog(&ab, &ctx.caller, h).0; }     // the documents open before the import: a failed import closes only the empty Untitled ones it made