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.36: read Altium's painted dialog text via UIA - the reaper was blind
7 files changed
+45−9
CHANGELOG.md+5@@ -1,5 +1,10 @@ # Changelog +## 0.1.36 — 2026-09-01+- **The bridge can finally READ Altium's dialog messages.** Altium PAINTS its dialog text instead of using Static controls, so `WM_GETTEXT` returned nothing but the BUTTON labels — `Could not start Vault Explorer…` came back as a body of just `"OK"`. Every such dialog therefore classified as `unknown`, which is why the standing reaper (correctly, per its own rules) refused to touch the exact warning it was built for.+- `_body_of()` now strips the button labels and, when what is left is too short to be a message, asks UIA (`desktop_ui_text`) for the painted text. Classification, the reaper, `check_dialogs` and every `_hint` that quotes a dialog all get the real message.+- This was the missing half of 0.1.35: the reaper was correct, its input was blind.+ ## 0.1.35 — 2026-09-01 - **A standing dialog reaper, running from bridge start.** John: *"and always pre-empt these from ever showing up."* Clearing a dialog after a verb notices it is not pre-emption — the user has already seen it. The reaper polls every 1.5 s and presses the catalog's SAFE action the instant a KNOWN nuisance appears, whether or not any verb is in flight. That is the only thing that can catch Altium's STARTUP warnings (`Could not start Vault Explorer`, `File type not recognised`), which appear with nothing running. - **Hard rules, so it can never answer for the user:** only dialogs whose catalog entry declares a safe action; never `unknown`; never a save prompt (those name documents that may belong to another AI thread and are surfaced for a human decision). Every press is recorded and readable via the new `altium_dialog_log`.
adom-bridge-altium-manifest.json+4−4@@ -1,9 +1,9 @@ { "name": "altium",- "version": "0.1.35",- "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.35/adom-bridge-altium-v0.1.35.zip",- "sha256": "4f050195f6dac158b7ab893e6b9da02d09bc40a0e874f72255a96bc73179fcb8",- "size": 134461,+ "version": "0.1.36",+ "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.36/adom-bridge-altium-v0.1.36.zip",+ "sha256": "8f549ccaf75788c179fe76112f17243a6a74ab83f6180ee1519f059eae8f5b94",+ "size": 134889, "verbPrefixes": [ "altium_" ],
adom-bridge-altium-v0.1.36.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "altium", "displayName": "Altium Designer",- "version": "0.1.35",+ "version": "0.1.36", "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.35",+ "version": "0.1.36", "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.35+0.1.36
src/handlers/dialogs.py+33−2@@ -94,6 +94,36 @@ def _text_of(hwnd: int) -> str: return " ".join(t.strip() for t in texts if t.strip()) +def _uia_text_of(hwnd: int) -> str:+ """Altium PAINTS its dialog messages instead of using Static controls, so WM_GETTEXT returns+ nothing but the BUTTON labels — 'Could not start Vault Explorer…' came back as just "OK", every+ such dialog classified as `unknown`, and the standing reaper (correctly) refused to touch it.+ UIA can see the painted text, so fall back to it whenever the local read is useless."""+ try:+ import caller_identity+ r = caller_identity.ad_callback("desktop_ui_text", {"hwnd": hwnd}, on_behalf=False, timeout=8) or {}+ except Exception:+ return ""+ v = r.get("text") or r.get("texts") or ""+ if isinstance(v, list):+ v = " ".join(str(x) for x in v if x)+ return str(v or "")+++def _body_of(hwnd: int, buttons: list) -> str:+ """The dialog's MESSAGE, with the button labels stripped — what classification needs."""+ body = _text_of(hwnd)+ labels = {str((b or {}).get("text") or "").strip().lower() for b in (buttons or [])}+ stripped = " ".join(w for w in body.split() if w.strip().lower() not in labels).strip()+ if len(stripped) < 12: # nothing but button labels: ask UIA+ ui = _uia_text_of(hwnd)+ if len(ui) > len(stripped):+ return ui+ return body++++ def _buttons_of(hwnd: int) -> list[dict]: if not IS_WINDOWS: return []@@ -152,10 +182,11 @@ def scan(main_pid_only: bool = False) -> list[dict]: modal_by_state = main_disabled and w.get("enabled") and w["pid"] == main_pid and w["className"] not in ("TClientBarControl", "TThemedNotificationPopup", "TAnimatedNotificationControl") if w["className"] not in DIALOG_CLASSES and w["kind"] not in ("dialog", "script_error_modal") and not modal_by_state: continue- body = _text_of(w["hwnd"])+ _btns = _buttons_of(w["hwnd"])+ body = _body_of(w["hwnd"], _btns) d = { "hwnd": w["hwnd"], "pid": w["pid"], "title": w["title"], "className": w["className"],- "body": body, "buttons": _buttons_of(w["hwnd"]),+ "body": body, "buttons": _btns, "ownedByMainInstance": (main_pid is not None and w["pid"] == main_pid), "rect": w["rect"], }