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.34: force-close guard fires on intent; instances reads Develop licence
8 files changed
+33−8
CHANGELOG.md+4@@ -1,5 +1,9 @@ # Changelog +## 0.1.34 — 2026-09-01+- **The force-close guard now fires on INTENT, not on a blocked dialog.** 0.1.33 put it inside the blocked-by-dialog branch, so `altium_close {force:true}` sailed straight through whenever nothing happened to block — exactly the path that strands the seat. It is now checked at the top of the verb: `force:true` on a licensed instance is refused with `would_strand_licence_seat` unless `acceptSeatLoss:true`.+- **`altium_instances` applies the Develop licence rule too.** 0.1.33 fixed only `licence_state()`, so the instance list still reported Altium Designer Develop as unlicensed while its agent was running and importing.+ ## 0.1.33 — 2026-09-01 - **Three new skills, because AI reads verb hints and skills, not tribal memory** (John: *"ai hints are way more valuable to help the ai know how to navigate the stuff you didn't know how to"*): - **altium-licensing** — one On-Demand seat serves EVERY Altium app (Designer and Designer Develop share it); killing an instance strands the seat and the licence page then reports it FREE while refusing every claim; the only recovery is a polite `altium_close` then `use_license`. Also: Develop writes NO licence tail in its title when it HOLDS the seat.
adom-bridge-altium-manifest.json+4−4@@ -1,9 +1,9 @@ { "name": "altium",- "version": "0.1.33",- "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.33/adom-bridge-altium-v0.1.33.zip",- "sha256": "7c53c2a5f79036f65f431145af27a412965e4a4c2384ca8e0a14d217ab22db00",- "size": 132257,+ "version": "0.1.34",+ "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.34/adom-bridge-altium-v0.1.34.zip",+ "sha256": "3dc30ed5d47d1a9dd95dda049ab0e3aa3884c11a938d9f79068a56e57ec6e613",+ "size": 132603, "verbPrefixes": [ "altium_" ],
adom-bridge-altium-v0.1.34.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "altium", "displayName": "Altium Designer",- "version": "0.1.33",+ "version": "0.1.34", "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.33",+ "version": "0.1.34", "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.33+0.1.34
src/handlers/altium_detect.py+7−1@@ -322,13 +322,19 @@ def instances() -> list[dict]: seen.add(w["pid"]) exe = _exe_of_pid(w["pid"]) st = w.get("licenceState")+ licensed = (st.lower() not in ("no license", "no licence", "viewer")) if st else None+ if st is None and (w.get("edition") == "Develop"):+ # Develop writes NO licence tail when it HOLDS the seat (only "- Viewer" when it does+ # not) — reading the absence as "unknown" reported ADD unlicensed for a whole test while+ # its agent imported five parts (2026-09-01).+ licensed, st = True, "Licensed (Develop reports no licence tail)" out.append({ "pid": w["pid"], "key": _key_for(exe, w["pid"]), "edition": w.get("edition") or "Designer", "adVersion": w.get("adVersion"), "licenceState": st,- "licensed": (st.lower() not in ("no license", "no licence", "viewer")) if st else None,+ "licensed": licensed, "exe": exe, "hwnd": w["hwnd"], "document": w.get("document"),
src/server.py+15@@ -234,6 +234,21 @@ def h_close(args): lic = altium_detect.licence_state() if not lic.get("running"): return {"success": True, "wasRunning": False}+ # The INTENT to force is the risk, not whether a dialog happens to block: force:true KILLS the+ # process, which strands the On-Demand seat (the licence page then shows Seats 0/1 while every+ # claim fails with "License maximum usage reached"). Refuse up front — a polite close releases+ # the seat and is almost always what the caller actually wants.+ if args.get("force") and not args.get("acceptSeatLoss") and lic.get("licensed") is not False:+ return _seat_hints({"success": False, "errorCode": "would_strand_licence_seat",+ "licenceState": lic.get("state"),+ "error": "force:true kills Altium and STRANDS its licence seat — no other Altium will be "+ "able to claim it afterwards, and the licence page will report it as free while "+ "refusing every claim.",+ "_hint": "Try altium_close with no force first: a polite WM_CLOSE releases the seat. If it "+ "reports blocked_by_dialog, READ the dialog (an Unsaved Changes prompt names the "+ "documents — on a shared machine one may belong to another AI thread) and answer "+ "it. Only if you truly must kill: repeat with acceptSeatLoss:true."},+ lic.get("state"), len(altium_detect.instances())) if agent.is_alive(): agent.stop() # polite first: WM_CLOSE on the main window (prompts for unsaved docs → dialogs), then taskkill