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
v0.1.31: unwrap ab reply envelopes (every callback read was silently None)
7 files changed
+37−8
CHANGELOG.md+4@@ -1,5 +1,9 @@ # Changelog +## 0.1.31 — 2026-09-01+- **Systemic fix: `ad_callback` now unwraps ab's reply envelope.** ab's direct API answers `{success, status, error, output}` with the verb's REAL result JSON-encoded as a STRING in `output`. The Python bridge never unwrapped it, so EVERY field read back from an ab callback — `hwnd`, `windows`, `processes`, `controls` — was silently `None`. Calls that only ACTED (click, kill) worked, which is why this hid for so long; anything that tried to READ a result quietly saw nothing.+- Found because 0.1.30's background-capture helper decided a minimized window was not minimized: it was reading `rect` out of an envelope it could not see into. The Rust bridge has always unwrapped (`ab.rs::unwrap_output`) — this brings Python in line.+ ## 0.1.30 — 2026-09-01 - **Proof captures work with Altium minimized, without ever moving the window.** Windows does not render a minimized window, so a capture of one returned a ~237x39 title-bar sliver — which the bridge handed back as "proof" of a successful import, and which the wiki front-end would have displayed as evidence. Now every capture first makes the window CAPTURABLE: restore it if minimized, push it to the BOTTOM of the z-order, and hand the user's foreground straight back. Measured end to end at ~0.6 s; the capture then reads the occluded window at full size. - **The rejected alternative, recorded so nobody rebuilds it:** parking the window off-screen. John, 2026-09-01 — *"i hate the idea of parking an altium window off screen cuz if the user wants to manually foreground, you fuck them over."* Never move a window the user might reach for.
adom-bridge-altium-manifest.json+4−4@@ -1,9 +1,9 @@ { "name": "altium",- "version": "0.1.30",- "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.30/adom-bridge-altium-v0.1.30.zip",- "sha256": "5c4909ca9c72037891762c97941b2ef57aaf9703684444176350cfe884dbdea7",- "size": 129499,+ "version": "0.1.31",+ "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.31/adom-bridge-altium-v0.1.31.zip",+ "sha256": "ff4f7b392d17e964d9cb1501edc45f85240a95928bf02f60209d7f749cda254c",+ "size": 129960, "verbPrefixes": [ "altium_" ],
adom-bridge-altium-v0.1.31.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "altium", "displayName": "Altium Designer",- "version": "0.1.30",+ "version": "0.1.31", "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",
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "altium-bridge",- "version": "0.1.30",+ "version": "0.1.31", "type": "app", "description": "Skills for your container so your AI knows how to drive the Altium Designer bridge (altium_* verbs). The bridge runtime itself is the release zip; Adom Bridge loads that.", "tags": [
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.1.30+0.1.31
src/caller_identity.py+26−1@@ -97,6 +97,31 @@ def self_caller() -> dict: return {"aiThread": "altium bridge (self)", "containerName": "local"} ++def _unwrap(v: dict) -> dict:+ """ab's direct API answers {success, status, error, output} with the verb's REAL result+ JSON-encoded as a STRING in `output`. Without unwrapping it, every field a caller reads back+ (`hwnd`, `windows`, `processes`, `controls`) is silently None — which is exactly how the+ background-capture helper decided a minimized window was fine, and why anything in this bridge+ that tried to READ an ab result quietly saw nothing (found 2026-09-01; the Rust bridge has+ always unwrapped). Outer status fields are kept when the inner object does not carry them."""+ if not isinstance(v, dict):+ return v+ out = v.get("output")+ if isinstance(out, str) and out.strip().startswith("{"):+ try:+ import json as _j+ inner = _j.loads(out)+ if isinstance(inner, dict):+ for k in ("success", "status", "error", "errorCode", "durationMs"):+ if k in v and k not in inner:+ inner[k] = v[k]+ return inner+ except Exception:+ pass+ return v++ def ad_callback(command: str, args: dict = None, on_behalf: bool = True, timeout: int = 30): """Call a Bridge verb back over the loopback direct API, forwarding the caller. @@ -127,6 +152,6 @@ def ad_callback(command: str, args: dict = None, on_behalf: bool = True, timeout req = urllib.request.Request(f"{base}/command", data=_json.dumps(body).encode(), headers=headers, method="POST") with urllib.request.urlopen(req, timeout=timeout) as r:- return _json.loads(r.read().decode() or "{}")+ return _unwrap(_json.loads(r.read().decode() or "{}")) except Exception as e: return {"success": False, "error": f"Bridge callback failed: {e}"}