Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

## Table of Contents

- [What's new (2026-06-19) — Popup Watchdog](#whats-new-2026-06-19--popup-watchdog)
- [What's new (2026-06-19) — Native UI Control](#whats-new-2026-06-19--native-ui-control)
- [What's new (2026-06-19)](#whats-new-2026-06-19)
- [What's new (2026-06-18)](#whats-new-2026-06-18)
Expand Down Expand Up @@ -60,6 +61,13 @@

---

## What's new (2026-06-19) — Popup Watchdog

The #1 cause of unattended-automation failure is an unexpected dialog the script never coded for (UAC, "session expiring", Windows Update, a modal). The popup watchdog runs a concurrent guard thread that watches for registered patterns and dismisses them independently of the main flow. Surfaced by the practitioner pain-point research as the top unattended failure cause; full stack (facade, `AC_*`, MCP, Script Builder), fully headless. Full reference: [`docs/source/Eng/doc/new_features/v8_features_doc.rst`](docs/source/Eng/doc/new_features/v8_features_doc.rst).

- **Auto-dismiss popups** — `default_popup_watchdog.add_window_rule(title, action="close")` then `.start()` (`AC_watchdog_add` / `AC_watchdog_start` / `AC_watchdog_stop` / `AC_watchdog_list`): closes a matching window or presses a key (`enter`/`esc`) when it appears.
- **Custom rules** — `PopupWatchdog` / `WatchdogRule` pair any detector (image/a11y/text) with a dismisser; a failing rule is logged and skipped, never killing the guard loop.

## What's new (2026-06-19) — Native UI Control

Object-level desktop automation: read and drive native controls through the OS accessibility API (by name / role / app / **AutomationId**) instead of clicking pixels or OCR-ing text — far more reliable for native apps. The accessibility layer previously only listed/found/clicked; it now also acts. Ships through the full stack (facade, `AC_*`, MCP, Script Builder) with a Windows UIAutomation backend; unsupported backends raise a clear error. Full reference: [`docs/source/Eng/doc/new_features/v7_features_doc.rst`](docs/source/Eng/doc/new_features/v7_features_doc.rst).
Expand Down
8 changes: 8 additions & 0 deletions README/README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

## 目录

- [本次更新 (2026-06-19) — 弹窗看门狗](#本次更新-2026-06-19--弹窗看门狗)
- [本次更新 (2026-06-19) — 原生 UI 控制](#本次更新-2026-06-19--原生-ui-控制)
- [本次更新 (2026-06-19)](#本次更新-2026-06-19)
- [本次更新 (2026-06-18)](#本次更新-2026-06-18)
Expand Down Expand Up @@ -59,6 +60,13 @@

---

## 本次更新 (2026-06-19) — 弹窗看门狗

无人值守自动化失败的第一大主因,是脚本没写到的意外对话框(UAC、"会话过期"、Windows Update、modal)。弹窗看门狗以并行守卫线程监看注册 pattern,独立于主流程把它们关掉。由社区痛点研究指出为无人值守头号失败主因;走完整五层(facade、`AC_*`、MCP、Script Builder),完全 headless。完整参考:[`docs/source/Eng/doc/new_features/v8_features_doc.rst`](../docs/source/Eng/doc/new_features/v8_features_doc.rst)。

- **自动关闭弹窗** — `default_popup_watchdog.add_window_rule(title, action="close")` 后 `.start()`(`AC_watchdog_add` / `AC_watchdog_start` / `AC_watchdog_stop` / `AC_watchdog_list`):窗口出现时关闭它或按键(`enter`/`esc`)。
- **自定义规则** — `PopupWatchdog` / `WatchdogRule` 把任意检测器(图/a11y/文本)配对关闭器;坏规则只记录并跳过,绝不让守卫循环停摆。

## 本次更新 (2026-06-19) — 原生 UI 控制

对象级桌面自动化:通过 OS 无障碍 API(以 name / role / app / **AutomationId** 定位)读取与操作原生控件,而非点像素或 OCR——对原生 app 可靠得多。无障碍层先前只能 list/find/click,现在还能操作。走完整五层(facade、`AC_*`、MCP、Script Builder),提供 Windows UIAutomation 后端;不支持的后端会抛清楚错误。完整参考:[`docs/source/Eng/doc/new_features/v7_features_doc.rst`](../docs/source/Eng/doc/new_features/v7_features_doc.rst)。
Expand Down
8 changes: 8 additions & 0 deletions README/README_zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

## 目錄

- [本次更新 (2026-06-19) — 彈窗看門狗](#本次更新-2026-06-19--彈窗看門狗)
- [本次更新 (2026-06-19) — 原生 UI 控制](#本次更新-2026-06-19--原生-ui-控制)
- [本次更新 (2026-06-19)](#本次更新-2026-06-19)
- [本次更新 (2026-06-18)](#本次更新-2026-06-18)
Expand Down Expand Up @@ -59,6 +60,13 @@

---

## 本次更新 (2026-06-19) — 彈窗看門狗

無人值守自動化失敗的第一大主因,是腳本沒寫到的未預期對話框(UAC、「工作階段過期」、Windows Update、modal)。彈窗看門狗以並行守衛執行緒監看註冊 pattern,獨立於主流程把它們關掉。由社群痛點研究指出為無人值守頭號失敗主因;走完整五層(facade、`AC_*`、MCP、Script Builder),完全 headless。完整參考:[`docs/source/Zh/doc/new_features/v8_features_doc.rst`](../docs/source/Zh/doc/new_features/v8_features_doc.rst)。

- **自動關閉彈窗** — `default_popup_watchdog.add_window_rule(title, action="close")` 後 `.start()`(`AC_watchdog_add` / `AC_watchdog_start` / `AC_watchdog_stop` / `AC_watchdog_list`):視窗出現時關閉它或按鍵(`enter`/`esc`)。
- **自訂規則** — `PopupWatchdog` / `WatchdogRule` 把任意偵測器(圖/a11y/文字)配對關閉器;壞規則只記錄並略過,絕不讓守衛迴圈停擺。

## 本次更新 (2026-06-19) — 原生 UI 控制

物件級桌面自動化:透過 OS 無障礙 API(以 name / role / app / **AutomationId** 定位)讀取與操作原生控制項,而非點像素或 OCR——對原生 app 可靠得多。無障礙層先前只能 list/find/click,現在還能操作。走完整五層(facade、`AC_*`、MCP、Script Builder),提供 Windows UIAutomation 後端;不支援的後端會拋清楚錯誤。完整參考:[`docs/source/Zh/doc/new_features/v7_features_doc.rst`](../docs/source/Zh/doc/new_features/v7_features_doc.rst)。
Expand Down
68 changes: 68 additions & 0 deletions docs/source/Eng/doc/new_features/v8_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
=============================================
New Features (2026-06-19) — Popup Watchdog
=============================================

The #1 reason unattended automation fails is an unexpected dialog the
script was never coded for — a UAC prompt, a "session expiring" banner, a
Windows Update toast, a newsletter modal. The popup watchdog runs a
concurrent guard thread that watches for registered patterns and dismisses
them *independently* of the main step sequence, so a long run keeps going.

Surfaced by the practitioner pain-point research as the top unattended
failure cause. Ships through the full stack (facade, ``AC_*`` executor
commands, MCP tools, Script Builder) and is fully headless — matchers and
actions are injectable, so it's unit-tested without a real desktop.

.. contents::
:local:
:depth: 2


Quick start
===========

::

from je_auto_control import default_popup_watchdog

# Auto-close any window whose title contains "Update Available".
default_popup_watchdog.add_window_rule("Update Available", action="close")
# Press Esc on a "Session expiring" dialog instead of closing it.
default_popup_watchdog.add_window_rule("Session expiring", action="esc")
default_popup_watchdog.start()
... # run your main flow
default_popup_watchdog.stop()

``action`` is ``"close"`` (close the matching window) or a key name to
press (``"enter"`` / ``"esc"`` / ...). The guard polls on a background
thread and records every dismissal in ``default_popup_watchdog.hits``.


Custom rules
============

For non-window popups, register a generic rule pairing a *detector* with a
*dismisser*::

from je_auto_control import PopupWatchdog, WatchdogRule

watchdog = PopupWatchdog(poll_interval_s=0.5)
watchdog.add_rule(WatchdogRule(
name="cookie-banner",
matcher=lambda: locate_image_center("cookie.png") is not None,
action=lambda: click_text("Accept"),
))

A rule whose ``matcher``/``action`` raises is logged and skipped — one bad
rule never kills the guard loop.


Executor commands
=================

* ``AC_watchdog_add`` — register a window rule (``title`` + ``action``).
* ``AC_watchdog_start`` / ``AC_watchdog_stop`` — control the guard thread.
* ``AC_watchdog_list`` — report run state, rules, and dismissals.

A typical unattended script adds its rules and starts the watchdog before
the main work, so any stray dialog is cleared automatically.
1 change: 1 addition & 0 deletions docs/source/Eng/eng_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Comprehensive guides for all AutoControl features.
doc/new_features/v5_features_doc
doc/new_features/v6_features_doc
doc/new_features/v7_features_doc
doc/new_features/v8_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
65 changes: 65 additions & 0 deletions docs/source/Zh/doc/new_features/v8_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
====================================
新功能 (2026-06-19) — 彈窗看門狗
====================================

無人值守自動化失敗的第一大主因,是腳本沒寫到的未預期對話框——UAC 提示、
「工作階段即將過期」橫幅、Windows Update 通知、電子報彈窗。彈窗看門狗以
並行的守衛執行緒監看註冊的 pattern,並在**獨立於主步驟序列**之外將其關閉,
讓長時間執行得以持續。

這由社群痛點研究指出為無人值守的頭號失敗主因。走完整五層(facade、
``AC_*`` 執行器指令、MCP 工具、Script Builder),且完全 headless——matcher
與 action 皆可注入,因此不需真實桌面即可單元測試。

.. contents::
:local:
:depth: 2


快速開始
========

::

from je_auto_control import default_popup_watchdog

# 自動關閉任何標題含「Update Available」的視窗。
default_popup_watchdog.add_window_rule("Update Available", action="close")
# 對「Session expiring」對話框改按 Esc,而非關閉。
default_popup_watchdog.add_window_rule("Session expiring", action="esc")
default_popup_watchdog.start()
... # 執行你的主流程
default_popup_watchdog.stop()

``action`` 為 ``"close"``(關閉相符視窗)或要按的鍵名(``"enter"`` /
``"esc"`` / ...)。守衛在背景執行緒輪詢,並把每次關閉記錄在
``default_popup_watchdog.hits``。


自訂規則
========

對於非視窗的彈窗,註冊一條把*偵測器*與*關閉器*配對的通用規則::

from je_auto_control import PopupWatchdog, WatchdogRule

watchdog = PopupWatchdog(poll_interval_s=0.5)
watchdog.add_rule(WatchdogRule(
name="cookie-banner",
matcher=lambda: locate_image_center("cookie.png") is not None,
action=lambda: click_text("Accept"),
))

matcher/action 拋出例外的規則會被記錄並略過——單一壞規則絕不會讓守衛迴圈
停擺。


執行器指令
==========

* ``AC_watchdog_add`` — 註冊視窗規則(``title`` + ``action``)。
* ``AC_watchdog_start`` / ``AC_watchdog_stop`` — 控制守衛執行緒。
* ``AC_watchdog_list`` — 回報執行狀態、規則與關閉紀錄。

典型的無人值守腳本會在主工作之前先加規則並啟動看門狗,讓任何雜散對話框
被自動清除。
1 change: 1 addition & 0 deletions docs/source/Zh/zh_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ AutoControl 所有功能的完整使用指南。
doc/new_features/v5_features_doc
doc/new_features/v6_features_doc
doc/new_features/v7_features_doc
doc/new_features/v8_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
5 changes: 5 additions & 0 deletions je_auto_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@
from je_auto_control.utils.hotkey.hotkey_daemon import (
HotkeyBinding, HotkeyDaemon, default_hotkey_daemon,
)
# Background popup/interrupt watchdog (unattended automation)
from je_auto_control.utils.watchdog import (
PopupWatchdog, WatchdogRule, default_popup_watchdog,
)
# OCR (headless)
from je_auto_control.utils.ocr.ocr_engine import (
TextMatch, click_text, find_text_matches, find_text_regex,
Expand Down Expand Up @@ -489,6 +493,7 @@ def start_autocontrol_gui(*args, **kwargs):
"get_clipboard", "set_clipboard",
# Hotkey daemon
"HotkeyDaemon", "HotkeyBinding", "default_hotkey_daemon",
"PopupWatchdog", "WatchdogRule", "default_popup_watchdog",
# MCP server
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",
Expand Down
18 changes: 18 additions & 0 deletions je_auto_control/gui/script_builder/command_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,24 @@ def _add_native_control_specs(specs: List[CommandSpec]) -> None:

def _add_misc_specs(specs: List[CommandSpec]) -> None:
_add_native_control_specs(specs)
specs.append(CommandSpec(
"AC_watchdog_add", "Flow", "Watchdog: Add Popup Rule",
fields=(
FieldSpec("title", FieldType.STRING),
FieldSpec("action", FieldType.STRING, optional=True,
default="close", placeholder="close / enter / esc"),
FieldSpec("case_sensitive", FieldType.BOOL, optional=True,
default=False),
FieldSpec("name", FieldType.STRING, optional=True),
),
description="Auto-dismiss an unexpected window when it appears.",
))
specs.append(CommandSpec(
"AC_watchdog_start", "Flow", "Watchdog: Start"))
specs.append(CommandSpec(
"AC_watchdog_stop", "Flow", "Watchdog: Stop"))
specs.append(CommandSpec(
"AC_watchdog_list", "Flow", "Watchdog: List Rules / Hits"))
specs.append(CommandSpec(
"AC_shell_command", "Shell", "Shell Command",
fields=(FieldSpec("shell_command", FieldType.STRING),),
Expand Down
36 changes: 36 additions & 0 deletions je_auto_control/utils/executor/action_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2257,6 +2257,38 @@ def _read_table(name: Optional[str] = None, role: Optional[str] = None,
automation_id=automation_id)


def _watchdog_add(title: str, action: str = "close",
case_sensitive: bool = False,
name: Optional[str] = None) -> Dict[str, Any]:
"""Adapter: register a popup-dismissal rule on the default watchdog."""
from je_auto_control.utils.watchdog import default_popup_watchdog
default_popup_watchdog.add_window_rule(
title, action=str(action), case_sensitive=bool(case_sensitive),
name=name)
return {"rules": default_popup_watchdog.rule_names()}


def _watchdog_start() -> Dict[str, Any]:
"""Adapter: start the background popup watchdog."""
from je_auto_control.utils.watchdog import default_popup_watchdog
default_popup_watchdog.start()
return {"running": True}


def _watchdog_stop() -> Dict[str, Any]:
"""Adapter: stop the background popup watchdog."""
from je_auto_control.utils.watchdog import default_popup_watchdog
default_popup_watchdog.stop()
return {"running": False}


def _watchdog_list() -> Dict[str, Any]:
"""Adapter: report the watchdog's rules, run state and dismissals."""
from je_auto_control.utils.watchdog import default_popup_watchdog
w = default_popup_watchdog
return {"running": w.running, "rules": w.rule_names(), "hits": w.hits}


class Executor:
"""
Executor
Expand Down Expand Up @@ -2409,6 +2441,10 @@ def __init__(self):
"AC_control_invoke": _control_invoke,
"AC_control_toggle": _control_toggle,
"AC_read_table": _read_table,
"AC_watchdog_add": _watchdog_add,
"AC_watchdog_start": _watchdog_start,
"AC_watchdog_stop": _watchdog_stop,
"AC_watchdog_list": _watchdog_list,
"AC_a11y_record_start": _a11y_record_start,
"AC_a11y_record_stop": _a11y_record_stop,
"AC_a11y_record_events": _a11y_record_events,
Expand Down
47 changes: 46 additions & 1 deletion je_auto_control/utils/mcp_server/tools/_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -1669,6 +1669,50 @@ def process_and_shell_tools() -> List[MCPTool]:
]


def watchdog_tools() -> List[MCPTool]:
return [
MCPTool(
name="ac_watchdog_add",
description=("Register a background popup-dismissal rule: when a "
"window whose title contains 'title' appears, the "
"watchdog closes it (action='close') or presses a key "
"(action='enter'/'esc'). Guards unattended runs "
"against unexpected dialogs (UAC, update prompts)."),
input_schema=schema({
"title": {"type": "string"},
"action": {"type": "string"},
"case_sensitive": {"type": "boolean"},
"name": {"type": "string"},
}, required=["title"]),
handler=h.watchdog_add,
annotations=SIDE_EFFECT_ONLY,
),
MCPTool(
name="ac_watchdog_start",
description=("Start the background popup watchdog (concurrent guard "
"thread that dismisses registered popups)."),
input_schema=schema({}),
handler=h.watchdog_start,
annotations=SIDE_EFFECT_ONLY,
),
MCPTool(
name="ac_watchdog_stop",
description="Stop the background popup watchdog.",
input_schema=schema({}),
handler=h.watchdog_stop,
annotations=SIDE_EFFECT_ONLY,
),
MCPTool(
name="ac_watchdog_list",
description=("Report the watchdog's run state, registered rules, "
"and the popups it has dismissed."),
input_schema=schema({}),
handler=h.watchdog_list,
annotations=READ_ONLY,
),
]


def hotkey_tools() -> List[MCPTool]:
return [
MCPTool(
Expand Down Expand Up @@ -2609,7 +2653,8 @@ def media_assert_tools() -> List[MCPTool]:
smart_wait_tools, cost_telemetry_tools, failure_hook_tools,
computer_use_tools, dag_tools, presence_tools, chatops_tools,
redaction_tools, android_widget_tools, ios_tools, webrunner_tools,
scheduler_tools, trigger_tools, hotkey_tools, screen_record_tools,
scheduler_tools, trigger_tools, hotkey_tools, watchdog_tools,
screen_record_tools,
process_and_shell_tools, remote_desktop_tools, gamepad_tools,
usb_passthrough_tools, assertion_tools, data_source_tools,
sql_tools, http_tools, email_tools, pdf_tools,
Expand Down
Loading
Loading