Skip to content

Commit ce969fa

Browse files
authored
Merge pull request #217 from Integration-Automation/feat/unattended-reliability
Add unattended/login reliability: OTP, file dialogs, session guard
2 parents 9a0ea4e + aa56d70 commit ce969fa

21 files changed

Lines changed: 559 additions & 3 deletions

File tree

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
## Table of Contents
1515

16+
- [What's new (2026-06-19) — Unattended Reliability](#whats-new-2026-06-19--unattended-reliability)
1617
- [What's new (2026-06-19) — Popup Watchdog](#whats-new-2026-06-19--popup-watchdog)
1718
- [What's new (2026-06-19) — Native UI Control](#whats-new-2026-06-19--native-ui-control)
1819
- [What's new (2026-06-19)](#whats-new-2026-06-19)
@@ -61,6 +62,14 @@
6162

6263
---
6364

65+
## What's new (2026-06-19) — Unattended Reliability
66+
67+
Three practitioner-pain fixes for unattended / login automation, all headless and full-stack. Full reference: [`docs/source/Eng/doc/new_features/v9_features_doc.rst`](docs/source/Eng/doc/new_features/v9_features_doc.rst).
68+
69+
- **OTP / TOTP for 2FA**`generate_totp` / `verify_totp` (`AC_otp_to_var`, `ac_generate_otp`): mint the current 6-digit code from a base32 secret to type into a login form (reuses the remote-desktop TOTP engine).
70+
- **Native file dialogs**`handle_file_dialog` (`AC_handle_file_dialog`): wait for the OS Open/Save/folder dialog, type the path, confirm — in one call, with an injectable driver.
71+
- **Locked-session guard**`ensure_interactive_session` / `is_session_locked` (`AC_assert_session_active`): fail clearly when the workstation is locked / disconnected instead of emitting phantom clicks.
72+
6473
## What's new (2026-06-19) — Popup Watchdog
6574

6675
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).

README/README_zh-CN.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目录
1414

15+
- [本次更新 (2026-06-19) — 无人值守可靠性](#本次更新-2026-06-19--无人值守可靠性)
1516
- [本次更新 (2026-06-19) — 弹窗看门狗](#本次更新-2026-06-19--弹窗看门狗)
1617
- [本次更新 (2026-06-19) — 原生 UI 控制](#本次更新-2026-06-19--原生-ui-控制)
1718
- [本次更新 (2026-06-19)](#本次更新-2026-06-19)
@@ -60,6 +61,14 @@
6061

6162
---
6263

64+
## 本次更新 (2026-06-19) — 无人值守可靠性
65+
66+
三个无人值守/登录自动化的社区痛点修复,均 headless 且走完整五层。完整参考:[`docs/source/Eng/doc/new_features/v9_features_doc.rst`](../docs/source/Eng/doc/new_features/v9_features_doc.rst)
67+
68+
- **2FA 的 OTP / TOTP**`generate_totp` / `verify_totp`(`AC_otp_to_var``ac_generate_otp`):从 base32 secret 生成当下 6 位码,填进登录表单(重用远程桌面 TOTP 引擎)。
69+
- **原生文件对话框**`handle_file_dialog`(`AC_handle_file_dialog`):等 OS 打开/保存/文件夹对话框、输入路径、确认,一次完成,driver 可注入。
70+
- **锁定会话守卫**`ensure_interactive_session` / `is_session_locked`(`AC_assert_session_active`):工作站锁定/断开时清楚失败,而非发出幽灵点击。
71+
6372
## 本次更新 (2026-06-19) — 弹窗看门狗
6473

6574
无人值守自动化失败的第一大主因,是脚本没写到的意外对话框(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)

README/README_zh-TW.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目錄
1414

15+
- [本次更新 (2026-06-19) — 無人值守可靠性](#本次更新-2026-06-19--無人值守可靠性)
1516
- [本次更新 (2026-06-19) — 彈窗看門狗](#本次更新-2026-06-19--彈窗看門狗)
1617
- [本次更新 (2026-06-19) — 原生 UI 控制](#本次更新-2026-06-19--原生-ui-控制)
1718
- [本次更新 (2026-06-19)](#本次更新-2026-06-19)
@@ -60,6 +61,14 @@
6061

6162
---
6263

64+
## 本次更新 (2026-06-19) — 無人值守可靠性
65+
66+
三個無人值守/登入自動化的社群痛點修復,皆 headless 且走完整五層。完整參考:[`docs/source/Zh/doc/new_features/v9_features_doc.rst`](../docs/source/Zh/doc/new_features/v9_features_doc.rst)
67+
68+
- **2FA 的 OTP / TOTP**`generate_totp` / `verify_totp`(`AC_otp_to_var``ac_generate_otp`):從 base32 secret 產生當下 6 碼,填進登入表單(重用遠端桌面 TOTP 引擎)。
69+
- **原生檔案對話框**`handle_file_dialog`(`AC_handle_file_dialog`):等 OS 開啟/儲存/資料夾對話框、輸入路徑、確認,一次完成,driver 可注入。
70+
- **鎖定工作階段守衛**`ensure_interactive_session` / `is_session_locked`(`AC_assert_session_active`):工作站鎖定/斷線時清楚失敗,而非送出幽靈點擊。
71+
6372
## 本次更新 (2026-06-19) — 彈窗看門狗
6473

6574
無人值守自動化失敗的第一大主因,是腳本沒寫到的未預期對話框(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)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
===================================================
2+
New Features (2026-06-19) — Unattended Reliability
3+
===================================================
4+
5+
Three practitioner-pain fixes for unattended and login automation: mint
6+
2FA codes, drive native file dialogs, and refuse to act on a locked
7+
screen. Each ships through the full stack (facade, ``AC_*`` executor
8+
commands, MCP tools, Script Builder) and is fully headless — external
9+
steps are deterministic or injectable, so they unit-test without 2FA, a
10+
real dialog, or a locked session.
11+
12+
.. contents::
13+
:local:
14+
:depth: 2
15+
16+
17+
OTP / TOTP for 2FA logins
18+
=========================
19+
20+
2FA blocks automated logins. Store the base32 secret (ideally in the
21+
secrets store) and mint the current code mid-flow::
22+
23+
from je_auto_control import generate_totp, verify_totp
24+
25+
code = generate_totp(secret) # 6-digit TOTP for "now"
26+
type_text(code)
27+
28+
``AC_otp_to_var`` writes the code into a flow variable for the next step::
29+
30+
["AC_otp_to_var", {"secret": "JBSWY3DPEHPK3PXP", "var": "otp"}]
31+
["AC_type_keyboard", {"keycode": "${otp}"}]
32+
33+
Reuses the TOTP engine that backs remote-desktop auth. Executor command:
34+
``AC_otp_to_var``; MCP tool: ``ac_generate_otp``.
35+
36+
37+
Native file dialogs
38+
===================
39+
40+
Recorders don't capture the OS file Open/Save/folder dialog, so everyone
41+
hand-rolls "type the path + Enter". ``handle_file_dialog`` does it in one
42+
call::
43+
44+
from je_auto_control import handle_file_dialog
45+
46+
handle_file_dialog("C:/reports/out.csv", action="save")
47+
48+
``action`` is ``open`` / ``save`` / ``folder`` (picking a default dialog
49+
title) or pass an explicit ``window_title``; it waits for the dialog,
50+
types the path, and presses ``confirm_key`` (default Enter). The
51+
window-wait / type / confirm steps go through an injectable
52+
:class:`FileDialogDriver`. Executor command: ``AC_handle_file_dialog``.
53+
54+
55+
Locked-session guard
56+
===================
57+
58+
Unattended runs silently fail when the workstation is locked or the RDP
59+
session is disconnected — input no-ops or throws. Check first::
60+
61+
from je_auto_control import ensure_interactive_session, is_session_locked
62+
63+
ensure_interactive_session() # raises if locked
64+
if is_session_locked():
65+
...
66+
67+
On Windows the probe opens the input desktop (which fails when locked);
68+
other platforms report "not locked" unless a custom probe is supplied.
69+
Executor command: ``AC_assert_session_active`` — put it at the top of an
70+
unattended script so it fails clearly instead of emitting phantom clicks.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Comprehensive guides for all AutoControl features.
3131
doc/new_features/v6_features_doc
3232
doc/new_features/v7_features_doc
3333
doc/new_features/v8_features_doc
34+
doc/new_features/v9_features_doc
3435
doc/ocr_backends/ocr_backends_doc
3536
doc/observability/observability_doc
3637
doc/operations_layer/operations_layer_doc
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
========================================
2+
新功能 (2026-06-19) — 無人值守可靠性
3+
========================================
4+
5+
三個針對無人值守與登入自動化的社群痛點修復:產生 2FA 驗證碼、操作原生
6+
檔案對話框、以及拒絕在鎖定畫面上動作。每項都走完整五層(facade、
7+
``AC_*`` 執行器指令、MCP 工具、Script Builder),且完全 headless——外部
8+
步驟皆為決定性或可注入,因此不需 2FA、真實對話框或鎖定工作階段即可單元
9+
測試。
10+
11+
.. contents::
12+
:local:
13+
:depth: 2
14+
15+
16+
2FA 登入的 OTP / TOTP
17+
=====================
18+
19+
2FA 會擋住自動登入。把 base32 secret 存好(最好放進 secrets store),在
20+
流程中即時產生當前驗證碼::
21+
22+
from je_auto_control import generate_totp, verify_totp
23+
24+
code = generate_totp(secret) # 當下的 6 碼 TOTP
25+
type_text(code)
26+
27+
``AC_otp_to_var`` 會把驗證碼寫進流程變數供下一步使用::
28+
29+
["AC_otp_to_var", {"secret": "JBSWY3DPEHPK3PXP", "var": "otp"}]
30+
["AC_type_keyboard", {"keycode": "${otp}"}]
31+
32+
重用支撐遠端桌面驗證的 TOTP 引擎。執行器指令:``AC_otp_to_var``;
33+
MCP 工具:``ac_generate_otp``。
34+
35+
36+
原生檔案對話框
37+
==============
38+
39+
錄製器抓不到 OS 的檔案開啟/儲存/資料夾對話框,大家只好手刻「輸入路徑 +
40+
Enter」。``handle_file_dialog`` 一次搞定::
41+
42+
from je_auto_control import handle_file_dialog
43+
44+
handle_file_dialog("C:/reports/out.csv", action="save")
45+
46+
``action`` 為 ``open`` / ``save`` / ``folder``(自動選預設對話框標題),
47+
或傳明確的 ``window_title``;它會等對話框、輸入路徑、按 ``confirm_key``
48+
(預設 Enter)。等視窗/輸入/確認三步透過可注入的 :class:`FileDialogDriver`。
49+
執行器指令:``AC_handle_file_dialog``。
50+
51+
52+
鎖定工作階段守衛
53+
================
54+
55+
無人值守在工作站鎖定或 RDP 斷線時會默默失敗——輸入會 no-op 或拋例外。
56+
先檢查::
57+
58+
from je_auto_control import ensure_interactive_session, is_session_locked
59+
60+
ensure_interactive_session() # 鎖定時拋例外
61+
if is_session_locked():
62+
...
63+
64+
Windows 上以開啟 input desktop 偵測(鎖定時會失敗);其他平台除非提供自訂
65+
probe,否則回報「未鎖定」。執行器指令:``AC_assert_session_active``——放在
66+
無人值守腳本最前面,讓它清楚地失敗,而非送出幽靈點擊。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ AutoControl 所有功能的完整使用指南。
3131
doc/new_features/v6_features_doc
3232
doc/new_features/v7_features_doc
3333
doc/new_features/v8_features_doc
34+
doc/new_features/v9_features_doc
3435
doc/ocr_backends/ocr_backends_doc
3536
doc/observability/observability_doc
3637
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@
9797
from je_auto_control.utils.hotkey.hotkey_daemon import (
9898
HotkeyBinding, HotkeyDaemon, default_hotkey_daemon,
9999
)
100+
# OTP/TOTP for automated 2FA logins
101+
from je_auto_control.utils.otp import (
102+
TOTPError, generate_secret, generate_totp, verify_totp,
103+
)
104+
# Native file Open/Save/folder dialog helper
105+
from je_auto_control.utils.file_dialog import (
106+
FileDialogDriver, handle_file_dialog,
107+
)
108+
# Locked / non-interactive session guard
109+
from je_auto_control.utils.session_guard import (
110+
ensure_interactive_session, is_session_locked,
111+
)
100112
# Background popup/interrupt watchdog (unattended automation)
101113
from je_auto_control.utils.watchdog import (
102114
PopupWatchdog, WatchdogRule, default_popup_watchdog,
@@ -494,6 +506,9 @@ def start_autocontrol_gui(*args, **kwargs):
494506
# Hotkey daemon
495507
"HotkeyDaemon", "HotkeyBinding", "default_hotkey_daemon",
496508
"PopupWatchdog", "WatchdogRule", "default_popup_watchdog",
509+
"generate_totp", "verify_totp", "generate_secret", "TOTPError",
510+
"handle_file_dialog", "FileDialogDriver",
511+
"ensure_interactive_session", "is_session_locked",
497512
# MCP server
498513
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
499514
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,35 @@ def _add_misc_specs(specs: List[CommandSpec]) -> None:
623623
"AC_watchdog_stop", "Flow", "Watchdog: Stop"))
624624
specs.append(CommandSpec(
625625
"AC_watchdog_list", "Flow", "Watchdog: List Rules / Hits"))
626+
specs.append(CommandSpec(
627+
"AC_otp_to_var", "Flow", "OTP (TOTP) into Variable",
628+
fields=(
629+
FieldSpec("secret", FieldType.STRING),
630+
FieldSpec("var", FieldType.STRING, default="otp"),
631+
FieldSpec("digits", FieldType.INT, optional=True, default=6),
632+
FieldSpec("step", FieldType.INT, optional=True, default=30),
633+
),
634+
description="Generate a TOTP 2FA code from a base32 secret.",
635+
))
636+
specs.append(CommandSpec(
637+
"AC_handle_file_dialog", "Native UI", "Handle File Dialog",
638+
fields=(
639+
FieldSpec("path", FieldType.STRING),
640+
FieldSpec("action", FieldType.ENUM,
641+
choices=("open", "save", "folder"),
642+
optional=True, default="open"),
643+
FieldSpec("window_title", FieldType.STRING, optional=True),
644+
FieldSpec("timeout_s", FieldType.FLOAT, optional=True,
645+
default=10.0),
646+
FieldSpec("confirm_key", FieldType.STRING, optional=True,
647+
default="enter"),
648+
),
649+
description="Wait for a native file dialog, type a path, confirm.",
650+
))
651+
specs.append(CommandSpec(
652+
"AC_assert_session_active", "Flow", "Assert Session Active",
653+
description="Fail if the session is locked / non-interactive.",
654+
))
626655
specs.append(CommandSpec(
627656
"AC_shell_command", "Shell", "Shell Command",
628657
fields=(FieldSpec("shell_command", FieldType.STRING),),

je_auto_control/utils/executor/action_executor.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2289,6 +2289,23 @@ def _watchdog_list() -> Dict[str, Any]:
22892289
return {"running": w.running, "rules": w.rule_names(), "hits": w.hits}
22902290

22912291

2292+
def _handle_file_dialog(path: str, action: str = "open",
2293+
window_title: Optional[str] = None,
2294+
timeout_s: float = 10.0,
2295+
confirm_key: str = "enter") -> Dict[str, Any]:
2296+
"""Adapter: wait for a native file dialog, type the path, confirm."""
2297+
from je_auto_control.utils.file_dialog import handle_file_dialog
2298+
return handle_file_dialog(path, action=action, window_title=window_title,
2299+
timeout_s=float(timeout_s),
2300+
confirm_key=confirm_key)
2301+
2302+
2303+
def _assert_session_active() -> Dict[str, Any]:
2304+
"""Adapter: raise unless the session is interactive (not locked)."""
2305+
from je_auto_control.utils.session_guard import ensure_interactive_session
2306+
return {"interactive": ensure_interactive_session()}
2307+
2308+
22922309
class Executor:
22932310
"""
22942311
Executor
@@ -2445,6 +2462,8 @@ def __init__(self):
24452462
"AC_watchdog_start": _watchdog_start,
24462463
"AC_watchdog_stop": _watchdog_stop,
24472464
"AC_watchdog_list": _watchdog_list,
2465+
"AC_handle_file_dialog": _handle_file_dialog,
2466+
"AC_assert_session_active": _assert_session_active,
24482467
"AC_a11y_record_start": _a11y_record_start,
24492468
"AC_a11y_record_stop": _a11y_record_stop,
24502469
"AC_a11y_record_events": _a11y_record_events,

0 commit comments

Comments
 (0)