← Commit history

v0.1.14: activate_document over the agent (UIA on the main window is >100s), skill corrected

John Lauer ·fac6eff4e0 ·1mo ago ·parent 5c2db14
8 files changed +41−28
CHANGELOG.md+4
@@ -1,5 +1,9 @@ # Changelog +## 0.1.14 — 2026-09-01+- `altium_activate_document` re-implemented over the AGENT instead of UIA. 0.1.13 shipped it as a UIA document-bar click; measured on a live box that call takes **>100 s** (ab's budget is 60 s) because Altium's main-window UIA tree is huge. The agent runs IN-PROCESS: ~4 s, no cursor, no OS foreground steal, and Altium activates an already-open document rather than reloading it. Verified: title switched to the target .PcbDoc and board verbs answered in 0.2 s.+- altium-uia skill corrected accordingly — the rule has two halves: **UIA for dialogs and WebView pages** (small trees, fast), **the resident agent for anything inside the application**. `desktop_click` only when neither can reach it.+ ## 0.1.13 — 2026-09-01 - **UIA first, cursor last.** John, watching the bridge drive his box: *"you keep turning on the cursor, but you could maybe use UIA to do those clicks"*. He was right — `altium_license` had been clicking hardcoded pixel coordinates since 0.1.x. Altium DOES expose a UIA tree, including the WebView2 License Management page (`automationId: UseLicenseButton`, invokable). The licence lane now invokes programmatically (no foreground, no cursor movement) and falls back to the image-space click only on a genuine UIA miss; the reply says which lane ran (`lane`, `windowEtiquette`). - **New verb `altium_activate_document {name}`** — every open document is an invokable button in Altium's document bar (`GUIBinderView.TGUIDocBarButton`, accessible name = the tab caption). Making a .PcbDoc current no longer needs `open_document` (which raises Altium's window) or a pixel click on the tab. 95 verbs.
adom-bridge-altium-manifest.json+4−4
@@ -1,9 +1,9 @@ {   "name": "altium",-  "version": "0.1.13",-  "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.13/adom-bridge-altium-v0.1.13.zip",-  "sha256": "9a17fd1747a1986f15e4525f5235f302a05dcc7a2087d903c84995a4f60de10a",-  "size": 104721,+  "version": "0.1.14",+  "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.14/adom-bridge-altium-v0.1.14.zip",+  "sha256": "af3d17f6e4e4819973e5c79ff2a2e81b6fdcc05aa8f0dbdce59b60f4e60c1e3e",+  "size": 104820,   "verbPrefixes": [     "altium_"   ],
adom-bridge-altium-v0.1.14.zipadded
⋯ 1 unchanged line ⋯
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "altium",   "displayName": "Altium Designer",-  "version": "0.1.13",+  "version": "0.1.14",   "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.13",+  "version": "0.1.14",   "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": [
skills/altium-uia/SKILL.md+12−8
@@ -4,8 +4,8 @@ user-invocable: false description: >-   DEVELOPER-only. Driving Altium Designer through Windows UI Automation (ab's desktop_ui_* verbs)   instead of synthetic clicks, and the measured catalog of what AD26 actually exposes: dialogs,-  main menus, the document bar (every open document is an invokable button), and the WebView2-  License Management page. Read BEFORE adding any verb that touches an Altium window, and before+  main menus, the document bar, and the WebView2 License Management page - plus where UIA is too slow+  to use and the agent wins instead. Read BEFORE adding any verb that touches an Altium window, and before   reaching for desktop_click. Trigger words — altium uia, ui automation, desktop_ui_click,   automation id, foreground, focus steal, cursor moved, click altium button, switch document tab,   license management click, altium control catalog.@@ -15,7 +15,8 @@ description: >-  Parent skill: [altium-bridge-dev](../altium-bridge-dev/SKILL.md). -**The rule: UIA first. `desktop_click` is the fallback, not the default.**+**The rule, in order: the resident AGENT for anything inside the application, UIA for dialogs and+WebView pages, `desktop_click` only when neither can reach it.**  `desktop_click` is SendInput — it FOREGROUNDS the target and MOVES the user's pointer. `desktop_ui_click` is a UIA Invoke: programmatic, no foreground, no focus steal, no cursor movement. John, 2026-09-01,@@ -29,17 +30,20 @@ do those clicks"*. He was right — the licence lane had been clicking hardcoded | **Dialogs** — Error / Warning / Confirm / "Connect to Workspace" | `desktop_ui_click {hwnd, name: "OK"\|"Choose a license"\|"Continue", role: "button"}` | Plain VCL forms (`TXPBitBtn`). Reliable and fast. This is how the busy-notice reaper and `dismiss_dialogs` work. | | **Main menus** | `MenuItem` by name: `File`, `View`, `Project`, `Window`, `Help` | All `invokable=true`. | | **Title bar** | `Preferences...`, `Share`, `Buy Now`, `Place a Comment`, `Open Home page`, and the workspace item (named after the workspace, e.g. `Adom`) | MenuItems, invokable. |-| **Document bar — every open document** | automationId `GUIBinderView.TGUIDocBarButton`, accessible name = the tab caption (`Bluetooth_Sentinel.PcbDoc`, `(2) License Management`) | **Invoking one makes that document CURRENT.** This is the quiet way to satisfy "board verbs need a .PcbDoc current" — see `altium_activate_document`. Captions carry Altium's `(n) ` prefixes, so match on a substring first. |+| **Document bar — every open document** | automationId `GUIBinderView.TGUIDocBarButton`, accessible name = the tab caption | Present in the tree, but **do not use it**: a `desktop_ui_click` by name against the main window measured **>100 s** (ab's verb budget is 60 s). Use `altium_activate_document`, which goes through the AGENT (in-process, ~4 s, no cursor, no OS foreground). | | **License Management (WebView2)** | `automationId: UseLicenseButton`, `invokable: true`; others by name (`Refresh`, `Roam`, `Release`, `Get License`, `Sign Out`) | Chromium exposes its a11y tree, so the page DOM IS reachable. Verified live: a UIA invoke reclaimed a stranded On-Demand seat with zero cursor movement. | | **Panels button** (bottom-right) | `GUIDocForm.TPanelsHolderXPButton`, name `Panels` | Invokable. | | Canvas / PCB + schematic editing area | NOT exposed | Custom-drawn. Geometry work goes through the resident agent's scripting API, never through input. |  ## Two costs, both paid for in this session -1. **UIA searches over Altium's tree are SLOW.** A scoped `desktop_find_control` on the main window-   took seconds, and a batch of eight speculative name lookups timed out at 4 minutes. Always scope-   with `hwnd`, cache automationIds in code (see `handlers/uia.py: IDS`), and never loop over names-   you are guessing at. Prefer a known automationId; fall back to ONE name search.+1. **UIA against Altium's MAIN window is slow enough to be unusable.** A `desktop_find_control` took+   seconds; eight speculative name lookups timed out at 4 minutes; a single `desktop_ui_click` by+   exact name on a document tab exceeded 100 s. The fast UIA surfaces are the ones with SMALL trees:+   dialogs (instant) and the licence WebView (`UseLicenseButton`, seconds). **So the rule has two+   halves: UIA for dialogs and WebView pages; the resident AGENT for anything inside the+   application** — it runs in-process, so it moves no cursor and steals no foreground either, and it+   is an order of magnitude faster. Reach for `desktop_click` only when neither can. 2. **Nothing reaches an app while a higher-integrity window owns the foreground.** A screensaver    counts: with `adom-screensaver.scr` in front, both `desktop_click` and `desktop_ui_*` were    refused with a UIPI error that explicitly says retrying will not help. Detect it
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.1.13+0.1.14
src/server.py+18−13
@@ -1056,20 +1056,25 @@ def h_license(args):   def h_activate_document(args):-    """Make an ALREADY-OPEN document current, quietly.+    """Make an ALREADY-OPEN document current — the quiet lane. -    The board verbs need a .PcbDoc current and the schematic verbs need a leaf sheet; until now the-    only lanes were altium_open_document (re-opens, and Altium raises its window) or a pixel click-    on the tab. Every open document is a UIA button in the document bar, so invoking it is-    programmatic: no cursor movement, no foreground steal (John, 2026-09-01)."""-    lic = altium_detect.licence_state()-    if not lic.get("running"):-        return {"success": False, "errorCode": "altium_not_running", "_hint": "altium_launch first."}-    name = args.get("name") or args.get("document") or args.get("caption")-    if not name:-        return {"success": False, "error": "name is required (a tab caption, e.g. 'Bluetooth_Sentinel.PcbDoc')",-                "_hint": "altium_window_info lists what is open."}-    return uia.activate_document(lic["hwnd"], name)+    Measured 2026-09-01: the document bar IS in Altium's UIA tree, but a `desktop_ui_click` by name+    against the main window takes >100 s (the tree is huge — dialogs and the licence WebView are+    fast only because theirs are small). The AGENT is the real quiet lane: it runs INSIDE Altium,+    so activating a document costs one in-process call (~4 s), moves no cursor and steals no OS+    foreground. Altium ACTIVATES an already-open document rather than re-reading it."""+    g = _need_licensed()+    if g:+        return g+    path = args.get("path") or args.get("document") or args.get("name")+    if not path:+        return {"success": False, "error": "path is required (the document's full path)",+                "_hint": "altium_window_info lists what is open; board verbs need a .PcbDoc current."}+    r = agent.call("open", {"path": path}, timeout=float(args.get("timeout", 60)))+    r["lane"] = "agent"+    r["windowEtiquette"] = "background"+    r.setdefault("_hint", "The document is now current. An already-open document is activated, not reloaded.")+    return r   # ---- licence lifecycle: status, ensure-licensed, trial setup (John 2026-08-31) ------------