Trial and Force Action Modes (Playwright-style)

actionability.act_when_ready has one behaviour: wait for the target to be actionable, then act (or raise on timeout). Real flows need two more modes that Playwright codified:

  • trial — run every actionability check but don’t perform the action; just report whether it would have acted. The dry run for “is this control ready?” without side effects.

  • force — skip the checks and act now, the deliberate escape hatch when the gate is wrong (a control the heuristics misjudge as occluded / disabled).

act_with_mode() adds both alongside the default gated (auto) behaviour, over the same injectable seams as the gate, so each mode is testable without a screen. Reuses actionability.wait_actionable(). Imports no PySide6.

Headless API

from je_auto_control import act_with_mode

bbox = lambda: (x, y, w, h)
click = lambda point: do_click(point[0], point[1])

act_with_mode(click, bbox, mode="auto")    # gate, then click if ready
report = act_with_mode(click, bbox, mode="trial")  # dry run, never clicks
if report["actionable"]:
    ...
act_with_mode(click, bbox, mode="force")   # click now, no checks

Every mode returns {mode, acted, actionable, reason, point, result}: acted says whether the action ran, actionable / reason come from the gate (trial reports these without acting), and result is the action’s return value. The actionability probes (region_sampler / enabled_probe / hit_tester) and config are forwarded to the gate as usual. An unknown mode raises ValueError.

Executor commands

AC_act_with_mode (x / y + mode / button{mode, acted, actionable, reason, point}) clicks a point under the chosen mode — trial is a dry-run probe that never clicks, force clicks unconditionally. It is the matching ac_act_with_mode MCP tool and a Script Builder command under Flow. act_with_mode() (which takes an arbitrary action) is the Python-API surface.