← Commit history

v0.1.32: licence gate respects the selected instance

John Lauer ·546763184b ·1mo ago ·parent 30bd407
7 files changed +30−11
CHANGELOG.md+4
@@ -1,5 +1,9 @@ # Changelog +## 0.1.32 — 2026-09-01+- **Instance-awareness now reaches the licence gate.** `main_window()` returned "the first Altium main window" regardless of the selected instance, and `licence_state()` → `_need_licensed()` are built on it — so with two Altiums running, every verb targeting one instance was judged against the OTHER instance's licence. Measured live: `altium_license_status {instance:"develop"}` correctly reported `Trial`, while `altium_agent_start {instance:"develop"}` was refused `not_licensed` because Designer held no seat.+- Verified with a real second instance (Altium Designer Develop): the layer distinguishes `AD26/Designer` from `ADDevelop/Develop`, gives each its own agent workspace, and REFUSES a verb with `ambiguous_instance` when no selector is given — rather than acting inside the other AI thread's Altium.+ ## 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.
adom-bridge-altium-manifest.json+4−4
@@ -1,9 +1,9 @@ {   "name": "altium",-  "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,+  "version": "0.1.32",+  "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.32/adom-bridge-altium-v0.1.32.zip",+  "sha256": "d382722088301629b1c1d11f3bd6f574d3370861fb25d891a6cf7ecf32d883fc",+  "size": 130236,   "verbPrefixes": [     "altium_"   ],
adom-bridge-altium-v0.1.32.zipadded
⋯ 1 unchanged line ⋯
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "altium",   "displayName": "Altium Designer",-  "version": "0.1.31",+  "version": "0.1.32",   "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.31",+  "version": "0.1.32",   "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.31+0.1.32
src/handlers/altium_detect.py+19−4
@@ -234,10 +234,25 @@ def altium_windows() -> list[dict]:   def main_window() -> dict | None:-    for w in altium_windows():-        if w["kind"] == "main":-            return w-    return None+    """The main window of the SELECTED instance, or the only one.++    Instance-awareness has to reach this far down: `licence_state()` is built on it, and+    `_need_licensed()` is built on that. With two Altiums running, returning "the first main window"+    meant every licence gate judged the WRONG instance — a verb targeting Develop was refused+    because Designer held no seat (measured 2026-09-01, right after the instance layer landed)."""+    wins = [w for w in altium_windows() if w["kind"] == "main"]+    if not wins:+        return None+    try:+        from handlers import agent as _agent+        sel = _agent.current_instance()+    except Exception:+        sel = None+    if sel and sel.get("pid"):+        for w in wins:+            if w.get("pid") == sel["pid"]:+                return w+    return wins[0]   def licence_state() -> dict: