New Features (2026-06-19) — Reactive Observer

A non-blocking screen observer: register watches on a region/predicate and get a callback (or run an action list) when the watched thing appears, vanishes, or changes. This is the complement to the blocking wait_for_* helpers — a flow can react to dialogs, progress, or status changes while doing other work (the SikuliX observe model).

Pure standard library; wired through the full stack (facade, AC_* executor commands, MCP tools, Script Builder).

Python API

from je_auto_control import ScreenObserver, image_predicate, EVENT_APPEAR

obs = ScreenObserver(poll_interval_s=0.5)
obs.add("error-dialog",
        image_predicate("error.png", threshold=0.9),
        on_event=lambda event, value: dismiss(),
        events=(EVENT_APPEAR,))
obs.start()                 # background polling thread
...
obs.stop()

Detection is decoupled from the screen: a watch’s predicate just returns the current value (truthy = present), so transition logic is unit-tested with synthetic values via poll_once(). Built-in predicate builders — image_predicate(), text_predicate(), pixel_predicate() — wrap the existing locate / OCR / pixel helpers.

Transitions: appear (absent -> present), vanish (present -> absent), change (present, value differs). Subscribe to a subset via events=.

Executor / MCP commands

  • AC_observe_add — watch kind (image / text / pixel) for event and run actions when it fires (the watchdog pattern, generalised to screen content).

  • AC_observe_remove / AC_observe_list — manage watches.

  • AC_observe_poll — evaluate every watch once and return fired events (deterministic, thread-free — ideal in scripts/tests).

  • AC_observe_start / AC_observe_stop — background poll thread.

The matching ac_observe_* MCP tools expose the same surface.