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.
Agent close watchdog: exit the resident loop the moment a user-initiated Altium close begins (#4 mitigation) #2
Mitigation for the user-initiated half of #4. Base: 0.2.5 source in this repo.
One file, src/handlers/agent.py.
The gap
altium_close already stops the agent BEFORE its WM_CLOSE, so the verb path is
safe. But a user pressing the window X goes straight to Altium: shutdown then
unloads the scripting engine under a LIVE agent loop, and the close can wedge.
That is the #4 incident: X2 lingered headless after the X, and the machine
hard-froze until the process was killed.
The change
A close watchdog, armed on both success paths of agent.start(): a daemon
thread that, while the agent is resident, polls every 2s for the CLOSING state
(main window gone while the process still lives, exactly what #4 observed) and
drops the agent stop file that instant, so the loop's next tick exits it during
the shutdown instead of wedging under it.
- Read-only detection (
running_pids+altium_windows) plus a file drop: no Altium call, no focus, nothing for a healthy session to notice. - Exits by itself when the agent stops normally (heartbeat gone) or Altium is fully gone; idempotent re-arm on the next agent start.
saw_windowguards the cold-start phase (pid up, window not yet created), so it can never fire during a launch.
Honest limits
This is a race, not a guarantee: if the engine teardown wedges the loop before its next tick, the stop-file drop lands too late. It shrinks the window from "always exposed" to "a 2s poll", which should catch the common case where the user closes an idle Altium. The full fix likely needs the agent itself to notice shutdown (DelphiScript side), which I did not touch: that template is actively churning and an untested exception path there is its own modal risk.
Testing
I cannot run this (Linux container; the bridge needs Windows + Altium). It
parses clean and only uses existing primitives (is_alive, running_pids,
altium_windows, the stop file). Suggested verification on a Windows box:
altium_launch, altium_agent_start, then press the X on Altium yourself; the
close should complete and agent_status should show the loop gone. Then the #4
repro (close with the agent resident) should no longer wedge an idle session.
Diff Skip to comments (1)
@@ -1,756 +1,814 @@⋯ 485 unchanged lines ⋯ stop() time.sleep(1.0) else:+ arm_close_watchdog() return {"success": True, "alreadyRunning": True, "status": status()} # a stale heartbeat / inbox / responses from a previous life must not confuse this one ws = workspace_dir()⋯ 35 unchanged lines ⋯ restored = 0 if user_fg and not _is_altium_hwnd(user_fg): restored = _restore_fg(user_fg, 3.0)+ arm_close_watchdog() return {"success": True, "startedInSec": round(time.time() - t0, 2), "install": inst, "status": status(), "windowEtiquette": "background" if restored or _fg() == user_fg else "foreground", "focusRestores": restored}⋯ 17 unchanged lines ⋯ return {"success": False, "error": "agent did not stop", "_hint": "the loop may be frozen behind a modal — altium_check_dialogs"} +# ---- user-initiated-close watchdog -----------------------------------------------+# altium_close tears the agent down BEFORE its WM_CLOSE, but a user pressing the+# window X goes straight to Altium: shutdown then unloads the scripting engine+# under a LIVE agent loop, and the close can wedge -- X2 lingered headless and+# hard-froze the desktop until it was killed (#4, 2026-09-02). While the agent+# is resident, watch for the closing state (main window gone, process still+# alive) and drop the stop file THAT INSTANT so the loop's next tick exits it+# during the shutdown instead of wedging under it. Read-only detection plus a+# file drop: no Altium call, no focus, nothing for a healthy session to notice.++_close_watch_lock = threading.Lock()+_close_watch: threading.Thread | None = None+++def _close_watch_loop() -> None:+ global _close_watch+ saw_window = False+ try:+ while True:+ time.sleep(2.0)+ if not is_alive():+ return # normal stop, or the loop died with Altium+ if not altium_detect.running_pids():+ return # Altium fully gone; nothing left to guard+ if altium_detect.altium_windows():+ saw_window = True+ continue+ if saw_window:+ # Window gone, process alive, agent resident: a close is in+ # progress. Exit the loop NOW, before the engine teardown+ # wedges under it. If the loop is already past helping, the+ # drop is harmless.+ try:+ (workspace_dir() / "stop").write_text("1")+ except OSError:+ pass+ return+ finally:+ with _close_watch_lock:+ _close_watch = None+++def arm_close_watchdog() -> None:+ """Keep a daemon watching for a user-initiated Altium close while the agent+ is resident. Idempotent; the thread exits by itself when the agent or+ Altium stops, and re-arms on the next agent start."""+ global _close_watch+ with _close_watch_lock:+ if _close_watch and _close_watch.is_alive():+ return+ _close_watch = threading.Thread(target=_close_watch_loop, daemon=True,+ name="altium-agent-close-watchdog")+ _close_watch.start()++ # ---- focus etiquette ------------------------------------------------------------- # Measured 2026-08-29: the -R RunScript bootstrap opens AdomAgent.PrjScr in the # editor and ACTIVATES Altium's window — a foreground steal. Capture the user's⋯ 198 unchanged lines ⋯ "for a dialog it opened." if alive else "Heartbeat stopped: a modal or a script error froze the engine. altium_check_dialogs, " "dismiss, then altium_agent_start.")}+
Comments
Log in to comment.