app
Altium - the Altium Bridge
Public Made by Adomby adom
Adom Bridge for Altium Designer: a resident DelphiScript agent gives the AI eval, the 1,100-command process launcher, all 8 EDA surfaces with screenshots, compile/ERC, DRC, OutJob outputs, native SchLib/PcbLib authoring with STEP 3D bodies, and dialog handling by PID.
← Commit history
0.2.27 source: parked-engine check before agent bootstrap
6 files changed
+25−26
bridge-rs/Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "altium-bridge"-version = "0.2.26"+version = "0.2.27" dependencies = [ "getrandom", "serde",
bridge-rs/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "altium-bridge"-version = "0.2.26"+version = "0.2.27" edition = "2021" description = "Adom altium-bridge — the bridge process, in Rust. Calls ab's verbs for the desktop; speaks file IPC to the resident DelphiScript agent inside Altium." license = "MIT"
bridge-rs/src/agent.rs+20−21@@ -116,8 +116,28 @@ fn rid() -> String { pub fn call(command: &str, params: &[(&str, String)], payload: Option<&str>, timeout: Duration) -> Value { if !is_alive() {+ // A dead agent with Altium still showing the agent's script is a script PARKED ON A+ // BREAKPOINT: Altium's debugger breaks on any raised exception, even one the agent's own+ // Try/Except handled, and Altium Develop does it with no error box. The command that raised+ // already answered, so nobody noticed; the NEXT verb (this one) would now start a second+ // script into a parked engine and block until ab's HTTP timeout, and so would every verb after+ // it (the 30-verb cascade the battery found, 2026-09-20). Free the engine first.+ let mut freed = Value::Null;+ if crate::health::is_running() && is_agent_script_title(&main_title()) {+ freed = crate::lifecycle::recover(&format!("auto: agent dead and the engine parked on the agent script before '{command}'"));+ crate::dialogs::note_event(json!({"title": "(silent script halt)", "category": "engine_parked_cleared", "pressed": "EditScript:Stop", "ok": true,+ "note": format!("the agent had stopped on a breakpoint without an error box; cleared before running '{command}'")}));+ } // parity with the Python bridge: bootstrap on demand, so a cold box needs no ceremony let started = crate::lifecycle::start(Duration::from_secs(45));+ if !freed.is_null() {+ let mut r = if started.get("success").and_then(|b| b.as_bool()) == Some(true) {+ call_in(&workspace_dir(), command, params, payload, timeout)+ } else { started.clone() };+ r["enginePreviouslyParked"] = json!(true);+ r["_parkedNote"] = json!("Before this verb the agent was found stopped on a script breakpoint with no error box (an earlier command raised an exception Altium's debugger broke on). The bridge cleared it and restarted the agent. altium_dialog_log shows when.");+ if started.get("success").and_then(|b| b.as_bool()) == Some(true) { return r; }+ } if started.get("success").and_then(|b| b.as_bool()) != Some(true) { let mut out = started; if let Some(o) = out.as_object_mut() {@@ -169,10 +189,6 @@ pub fn call_in(ws: &std::path::Path, command: &str, params: &[(&str, String)], p let t_post = t0.elapsed().as_millis() as u64; let posted_unix_ms = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).map(|x| x.as_millis() as u64).unwrap_or(0); let mut next_halt_check = Duration::from_millis(400);- // Title of Altium's main window when the command was posted. When a script breaks, Altium- // pulls the agent's own script document to the front, with or WITHOUT an Error box (Altium- // Develop broke silently on board_shape, 2026-09-20: no box, so the box-watcher saw nothing).- let title_at_post = main_title(); let resp = ws.join(format!("response_{id}.json")); let mut t_seen: Option<u64> = None; let mut parse_retries = 0u32;@@ -200,23 +216,6 @@ pub fn call_in(ws: &std::path::Path, command: &str, params: &[(&str, String)], p // not leave Altium parked in its debugger: stop the script now and say what happened. if t0.elapsed() >= next_halt_check { next_halt_check = t0.elapsed() + Duration::from_millis(400);- // Variant 3, the SILENT halt: no box at all, but Altium switched its window to the- // agent's script (AdomAgent_*.pas / .PrjScr) while our command was running. Give the box- // watcher 1.5 s to claim it first (it carries the error text); after that, this is it.- if t0.elapsed() > Duration::from_millis(1500) && !resp.exists() {- let now_title = main_title();- if is_agent_script_title(&now_title) && !is_agent_script_title(&title_at_post) {- let shot = crate::shot::shot_of_main();- let recovered = crate::lifecycle::recover(&format!("auto: silent script halt during agent command '{command}'"));- return json!({"success": false, "errorCode": "agent_script_error", "silentHalt": true, "command": command,- "error": format!("the agent script halted while running '{command}': Altium raised no error box, but it stopped in its script debugger and pulled the agent script to the front"),- "errorShot": shot, "windowTitle": now_title,- "autoRecovered": recovered.get("success").and_then(|b| b.as_bool()).unwrap_or(false),- "recoverSteps": recovered.get("steps").cloned().unwrap_or(Value::Null),- "agentAlive": is_alive(),- "_hint": "errorShot is Altium's script editor at the moment of the halt (the highlighted line is where the engine stopped, usually the agent loop, not the faulty statement). The bridge already sent EditScript:Stop, so Altium is free and the next verb restarts the agent. Do not retry the same command: it will halt again. Report it on adom/altium-bridge with the command and its args."});- }- } // Variant 2 (script run from INSIDE the agent, e.g. eval): Altium asks "Undeclared // identifier ... Continue execution?" in an 'X2' box with OK + Cancel. It is OURS (it // only exists while our command runs), the reaper rightly never touches a two-button
bridge-rs/src/main.rs+1−1@@ -25,7 +25,7 @@ mod win32; use std::time::Instant; use tiny_http::{Header, Method, Response, Server}; -pub const BRIDGE_VERSION: &str = "0.2.26";+pub const BRIDGE_VERSION: &str = "0.2.27"; fn json_response(status: u16, body: &serde_json::Value) -> Response<std::io::Cursor<Vec<u8>>> { let data = serde_json::to_vec(body).unwrap_or_else(|_| b"{}".to_vec());
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "altium", "displayName": "Altium Designer",- "version": "0.2.26",+ "version": "0.2.27", "description": "Drive Altium Designer from Adom: a resident DelphiScript agent (eval + the 1,100-command process launcher), the X2 -R command line, UIA over the VCL shell, dialogs by PID, screenshots of every Altium window, and Altium 365.", "homepage": "https://wiki.adom.inc/adom/altium-bridge", "docs": "https://wiki.adom.inc/adom/altium-bridge",
page.json+1−1@@ -140,4 +140,4 @@ "skills/altium-wiki-import/SKILL.md", "uninstall.sh" ]-}+}