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) — Authoring & Debugging](#whats-new-2026-06-19--authoring--debugging)
- [What's new (2026-06-19) — Test & Tooling Batch](#whats-new-2026-06-19--test--tooling-batch)
- [What's new (2026-06-19) — Transactional Queue](#whats-new-2026-06-19--transactional-queue)
- [What's new (2026-06-19) — Unattended Reliability](#whats-new-2026-06-19--unattended-reliability)
Expand Down Expand Up @@ -64,6 +65,13 @@

---

## What's new (2026-06-19) — Authoring & Debugging

Two pure-stdlib authoring-time tools, full stack (facade, `AC_*`, MCP, Script Builder). Full reference: [`docs/source/Eng/doc/new_features/v12_features_doc.rst`](docs/source/Eng/doc/new_features/v12_features_doc.rst).

- **Element repository** — `ElementRepository` (`AC_element_save` / `AC_element_find` / `AC_element_click` / `AC_element_remove` / `AC_element_list`, `ac_element_*`): save native-UI locators under friendly names (object repository) and reuse them — `repo.click("login.submit")` instead of repeating name/role everywhere; a UI change is fixed in one place.
- **Step debugger / tracer** — `FlowDebugger` (breakpoints, `step`/`continue_`/`run_to_end`, live `variables()`) and `trace_actions` (`AC_debug_trace`, `ac_debug_trace`): step through an action list one command at a time with variables persisting across steps, or get a per-step `{index, command, result}` trace (with `dry_run` to plan without running).

## What's new (2026-06-19) — Test & Tooling Batch

Three pure-stdlib quality-of-life tools, full stack (facade, `AC_*`, MCP, Script Builder). Full reference: [`docs/source/Eng/doc/new_features/v11_features_doc.rst`](docs/source/Eng/doc/new_features/v11_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) — 测试与工具三件套](#本次更新-2026-06-19--测试与工具三件套)
- [本次更新 (2026-06-19) — 事务式工作队列](#本次更新-2026-06-19--事务式工作队列)
- [本次更新 (2026-06-19) — 无人值守可靠性](#本次更新-2026-06-19--无人值守可靠性)
Expand Down Expand Up @@ -63,6 +64,13 @@

---

## 本次更新 (2026-06-19) — 编写与调试

两项纯标准库的编写期工具,走完整五层(facade、`AC_*`、MCP、Script Builder)。完整参考:[`docs/source/Zh/doc/new_features/v12_features_doc.rst`](../docs/source/Zh/doc/new_features/v12_features_doc.rst)。

- **元素库** — `ElementRepository`(`AC_element_save` / `AC_element_find` / `AC_element_click` / `AC_element_remove` / `AC_element_list`、`ac_element_*`):把原生 UI 定位器以友好名称存起来(object repository)重用——用 `repo.click("login.submit")` 取代到处重复 name/role;UI 变动只需改一处。
- **步进调试器 / 追踪器** — `FlowDebugger`(断点、`step`/`continue_`/`run_to_end`、实时 `variables()`)与 `trace_actions`(`AC_debug_trace`、`ac_debug_trace`):把动作列表一次跑一个指令、变量跨步保留,或获取每步 `{index, command, result}` 追踪(用 `dry_run` 只规划不执行)。

## 本次更新 (2026-06-19) — 测试与工具三件套

三项纯标准库的生产力工具,走完整五层(facade、`AC_*`、MCP、Script Builder)。完整参考:[`docs/source/Zh/doc/new_features/v11_features_doc.rst`](../docs/source/Zh/doc/new_features/v11_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) — 測試與工具三件套](#本次更新-2026-06-19--測試與工具三件套)
- [本次更新 (2026-06-19) — 交易式工作佇列](#本次更新-2026-06-19--交易式工作佇列)
- [本次更新 (2026-06-19) — 無人值守可靠性](#本次更新-2026-06-19--無人值守可靠性)
Expand Down Expand Up @@ -63,6 +64,13 @@

---

## 本次更新 (2026-06-19) — 編寫與除錯

兩項純標準庫的編寫期工具,走完整五層(facade、`AC_*`、MCP、Script Builder)。完整參考:[`docs/source/Zh/doc/new_features/v12_features_doc.rst`](../docs/source/Zh/doc/new_features/v12_features_doc.rst)。

- **元素庫** — `ElementRepository`(`AC_element_save` / `AC_element_find` / `AC_element_click` / `AC_element_remove` / `AC_element_list`、`ac_element_*`):把原生 UI 定位器以友善名稱存起來(object repository)重用——用 `repo.click("login.submit")` 取代到處重複 name/role;UI 變動只需改一處。
- **步進除錯器 / 追蹤器** — `FlowDebugger`(中斷點、`step`/`continue_`/`run_to_end`、即時 `variables()`)與 `trace_actions`(`AC_debug_trace`、`ac_debug_trace`):把動作清單一次跑一個指令、變數跨步保留,或取得每步 `{index, command, result}` 追蹤(用 `dry_run` 只規劃不執行)。

## 本次更新 (2026-06-19) — 測試與工具三件套

三項純標準庫的生產力工具,走完整五層(facade、`AC_*`、MCP、Script Builder)。完整參考:[`docs/source/Zh/doc/new_features/v11_features_doc.rst`](../docs/source/Zh/doc/new_features/v11_features_doc.rst)。
Expand Down
66 changes: 66 additions & 0 deletions docs/source/Eng/doc/new_features/v12_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
====================================================
New Features (2026-06-19) — Authoring & Debugging
====================================================

Two authoring-time tools, pure standard library and wired through the
full stack (facade, ``AC_*`` executor commands, MCP tools, Script
Builder): a native-UI **element repository** (object repository) and a
**step debugger / tracer** for action lists.

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


Element repository
=================

Save native-UI locators under friendly names once, reuse them everywhere
— the classic RPA *object repository*. A flow references
``"login.submit"`` instead of repeating ``name="Submit", role="button"``
at every call site, and a UI change is fixed in one place::

from je_auto_control import ElementRepository

repo = ElementRepository("app.objects.json")
repo.save("login.submit", name="Submit", role="button")
repo.save("login.user", role="edit", app_name="MyApp")

repo.click("login.submit") # resolve + click the live element
info = repo.find_info("login.user") # {found, name, role, center}

A locator is a small set of accessibility filters (``name`` / ``role`` /
``app_name``); resolving finds the live element through the accessibility
backend. Storage is a JSON file and works on any platform; resolution
needs a platform accessibility backend.

Executor / MCP commands: ``AC_element_save`` / ``AC_element_find`` /
``AC_element_click`` / ``AC_element_remove`` / ``AC_element_list`` (and
the matching ``ac_element_*`` MCP tools).


Step debugger and tracer
=======================

Run an action list one command at a time with breakpoints, single-step,
and live variable inspection. Stepping reuses one executor instance, so
script variables (``${name}`` interpolation, ``AC_set_var`` …) persist
across steps exactly as in a normal run::

from je_auto_control import FlowDebugger

dbg = FlowDebugger(actions, breakpoints=[3])
dbg.continue_() # run until the breakpoint
dbg.variables() # inspect live variables
dbg.step() # one command at a time
dbg.run_to_end()

The stateless one-shot form, :func:`trace_actions`, runs a list (or
``dry_run`` to only plan it) and returns a per-step trace of
``{index, command, result}`` — exposed as ``AC_debug_trace`` /
``ac_debug_trace``::

from je_auto_control import trace_actions

plan = trace_actions(actions, dry_run=True) # plan without running
trace = trace_actions(actions) # run and trace
1 change: 1 addition & 0 deletions docs/source/Eng/eng_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Comprehensive guides for all AutoControl features.
doc/new_features/v9_features_doc
doc/new_features/v10_features_doc
doc/new_features/v11_features_doc
doc/new_features/v12_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
61 changes: 61 additions & 0 deletions docs/source/Zh/doc/new_features/v12_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
========================================
新功能 (2026-06-19) — 編寫與除錯
========================================

兩項編寫期工具,皆為純標準庫,並走完整五層(facade、``AC_*`` 執行器
指令、MCP 工具、Script Builder):原生 UI 的**元素庫**(object
repository)與動作清單的**步進除錯器 / 追蹤器**。

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


元素庫
======

把原生 UI 定位器以友善名稱存一次、到處重用——這就是 RPA 經典的
*object repository*。流程改以 ``"login.submit"`` 引用,而不必在每個
呼叫點重複 ``name="Submit", role="button"``;UI 變動只需改一處::

from je_auto_control import ElementRepository

repo = ElementRepository("app.objects.json")
repo.save("login.submit", name="Submit", role="button")
repo.save("login.user", role="edit", app_name="MyApp")

repo.click("login.submit") # 解析並點擊實際元素
info = repo.find_info("login.user") # {found, name, role, center}

定位器是一組小的 accessibility 過濾條件(``name`` / ``role`` /
``app_name``);解析時透過 accessibility 後端找到實際元素。儲存為 JSON
檔、跨平台可用;解析需要平台的 accessibility 後端。

執行器 / MCP 指令:``AC_element_save`` / ``AC_element_find`` /
``AC_element_click`` / ``AC_element_remove`` / ``AC_element_list``
(以及對應的 ``ac_element_*`` MCP 工具)。


步進除錯器與追蹤器
==================

把動作清單一次跑一個指令,具備中斷點、單步與即時變數檢視。步進時重用
同一個執行器實例,因此腳本變數(``${name}`` 插值、``AC_set_var`` …)
會像正常執行一樣在各步之間保留::

from je_auto_control import FlowDebugger

dbg = FlowDebugger(actions, breakpoints=[3])
dbg.continue_() # 跑到中斷點
dbg.variables() # 檢視即時變數
dbg.step() # 一次一個指令
dbg.run_to_end()

無狀態的一次性形式 :func:`trace_actions` 會跑完清單(或用 ``dry_run``
只做規劃),回傳每步的 ``{index, command, result}`` 追蹤——對應
``AC_debug_trace`` / ``ac_debug_trace``::

from je_auto_control import trace_actions

plan = trace_actions(actions, dry_run=True) # 只規劃不執行
trace = trace_actions(actions) # 執行並追蹤
1 change: 1 addition & 0 deletions docs/source/Zh/zh_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ AutoControl 所有功能的完整使用指南。
doc/new_features/v9_features_doc
doc/new_features/v10_features_doc
doc/new_features/v11_features_doc
doc/new_features/v12_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
6 changes: 6 additions & 0 deletions je_auto_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@
from je_auto_control.utils.mcp_registry import (
build_server_manifest, write_server_manifest,
)
# Named locator repository (object repository) for native UI
from je_auto_control.utils.element_repository import ElementRepository
# Step-through debugger / tracer for action lists
from je_auto_control.utils.flow_debugger import FlowDebugger, trace_actions
# Background popup/interrupt watchdog (unattended automation)
from je_auto_control.utils.watchdog import (
PopupWatchdog, WatchdogRule, default_popup_watchdog,
Expand Down Expand Up @@ -525,6 +529,8 @@ def start_autocontrol_gui(*args, **kwargs):
"generate_rows", "write_dataset",
"rank_flows", "select_flows",
"build_server_manifest", "write_server_manifest",
"ElementRepository",
"FlowDebugger", "trace_actions",
# MCP server
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",
Expand Down
40 changes: 40 additions & 0 deletions je_auto_control/gui/script_builder/command_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@
FieldSpec("automation_id", FieldType.STRING, optional=True),
)
specs.append(CommandSpec(
"AC_control_get_value", "Native UI", "Get Control Value",

Check failure on line 580 in je_auto_control/gui/script_builder/command_schema.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "Native UI" 11 times.

See more on https://sonarcloud.io/project/issues?id=Integration-Automation_AutoControlGUI&issues=AZ7dJ6mOyH2VcxfB2TF1&open=AZ7dJ6mOyH2VcxfB2TF1&pullRequest=220
fields=fields,
description="Read a native control's value via the accessibility API.",
))
Expand Down Expand Up @@ -654,6 +654,46 @@
))
_add_work_queue_specs(specs)
_add_tooling_specs(specs)
_add_authoring_specs(specs)


def _add_authoring_specs(specs: List[CommandSpec]) -> None:
path = FieldSpec("path", FieldType.FILE_PATH)
key = FieldSpec("key", FieldType.STRING)
specs.append(CommandSpec(
"AC_element_save", "Native UI", "Element: Save Locator",
fields=(path, key,
FieldSpec("name", FieldType.STRING, optional=True),
FieldSpec("role", FieldType.STRING, optional=True),
FieldSpec("app_name", FieldType.STRING, optional=True)),
description="Save a named native-UI locator (object repository).",
))
specs.append(CommandSpec(
"AC_element_find", "Native UI", "Element: Find Saved",
fields=(path, key),
description="Resolve a saved locator to a live element summary.",
))
specs.append(CommandSpec(
"AC_element_click", "Native UI", "Element: Click Saved",
fields=(path, key),
description="Click the element behind a saved locator.",
))
specs.append(CommandSpec(
"AC_element_remove", "Native UI", "Element: Remove Saved",
fields=(path, key),
description="Delete a saved locator.",
))
specs.append(CommandSpec(
"AC_element_list", "Native UI", "Element: List Saved",
fields=(path,),
description="List saved locator names in a repository file.",
))
specs.append(CommandSpec(
"AC_debug_trace", "Flow", "Debug: Trace Actions",
fields=(FieldSpec("dry_run", FieldType.BOOL, optional=True,
default=False),),
description="Run 'actions' (JSON view) and return a per-step trace.",
))


def _add_tooling_specs(specs: List[CommandSpec]) -> None:
Expand Down
6 changes: 6 additions & 0 deletions je_auto_control/utils/element_repository/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Named locator repository (object repository) for native-UI elements."""
from je_auto_control.utils.element_repository.element_repository import (
ElementRepository,
)

__all__ = ["ElementRepository"]
99 changes: 99 additions & 0 deletions je_auto_control/utils/element_repository/element_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Named locator repository (the classic RPA *object repository*).

Save native-UI locators under friendly names once, reuse them everywhere
— so flows reference ``"login.submit"`` instead of repeating
``name="Submit", role="button"`` at every call site, and a UI change is
fixed in one place. A locator is a small set of accessibility filters
(``name`` / ``role`` / ``app_name``); resolving it finds the live element
through the accessibility backend.

Pure standard library (JSON file storage); imports no ``PySide6``. The
accessibility backend is imported lazily so storage works on any platform.
"""
import json
from pathlib import Path
from typing import Any, Dict, List, Optional

_FILTER_FIELDS = ("name", "role", "app_name")


class ElementRepository:
"""A JSON-backed map of friendly name -> accessibility locator."""

def __init__(self, path: str) -> None:
self._path = Path(path)
self._items: Dict[str, Dict[str, str]] = self._load()

def _load(self) -> Dict[str, Dict[str, str]]:
if not self._path.exists():
return {}
data = json.loads(self._path.read_text(encoding="utf-8"))
if not isinstance(data, dict):
raise ValueError(f"{self._path} is not a locator map")
return {str(k): dict(v) for k, v in data.items()}

def _flush(self) -> None:
self._path.write_text(
json.dumps(self._items, indent=2, ensure_ascii=False),
encoding="utf-8")

def save(self, key: str, *, name: Optional[str] = None,
role: Optional[str] = None,
app_name: Optional[str] = None) -> Dict[str, str]:
"""Store a locator under ``key``; needs at least one filter."""
locator = {field: value for field, value in
(("name", name), ("role", role), ("app_name", app_name))
if value is not None}
if not locator:
raise ValueError("a locator needs at least one of name/role/"
"app_name")
self._items[str(key)] = locator
self._flush()
return dict(locator)

def get(self, key: str) -> Optional[Dict[str, str]]:
"""Return the stored locator for ``key`` (a copy) or ``None``."""
locator = self._items.get(str(key))
return dict(locator) if locator is not None else None

def remove(self, key: str) -> bool:
"""Delete a locator; return whether it existed."""
existed = str(key) in self._items
if existed:
del self._items[str(key)]
self._flush()
return existed

def keys(self) -> List[str]:
"""Return the saved locator names, sorted."""
return sorted(self._items)

def all(self) -> Dict[str, Dict[str, str]]:
"""Return a copy of the whole repository."""
return {key: dict(value) for key, value in self._items.items()}

def _require(self, key: str) -> Dict[str, str]:
locator = self.get(key)
if locator is None:
raise KeyError(f"no locator named {key!r}")
return locator

def resolve(self, key: str) -> Any:
"""Find the live element for ``key`` (or ``None`` if not present)."""
from je_auto_control.utils.accessibility import (
find_accessibility_element)
return find_accessibility_element(**self._require(key))

def find_info(self, key: str) -> Dict[str, Any]:
"""Resolve ``key`` and return a serialisable summary."""
element = self.resolve(key)
if element is None:
return {"found": False}
return {"found": True, "name": element.name, "role": element.role,
"center": list(element.center)}

def click(self, key: str) -> bool:
"""Click the element for ``key``; return whether it matched."""
from je_auto_control.utils.accessibility import (
click_accessibility_element)
return click_accessibility_element(**self._require(key))
Loading
Loading