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
7 changes: 7 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) — Process-Doc (SOP) Generator](#whats-new-2026-06-19--process-doc-sop-generator)
- [What's new (2026-06-19) — Heal Analytics & Secret Scan](#whats-new-2026-06-19--heal-analytics--secret-scan)
- [What's new (2026-06-19) — CI Annotations & Clipboard History](#whats-new-2026-06-19--ci-annotations--clipboard-history)
- [What's new (2026-06-19) — Resilience Primitives](#whats-new-2026-06-19--resilience-primitives)
Expand Down Expand Up @@ -80,6 +81,12 @@

---

## What's new (2026-06-19) — Process-Doc (SOP) Generator

Turn an action list into a step-by-step SOP. Full reference: [`docs/source/Eng/doc/new_features/v28_features_doc.rst`](docs/source/Eng/doc/new_features/v28_features_doc.rst).

- **`generate_sop` / `write_sop`** (`AC_generate_sop`, `ac_generate_sop`): map a recorded/authored action list to numbered, human-readable steps + an HTML document (UiPath Task-Capture deliverable); content HTML-escaped, unknown commands degrade gracefully.

## What's new (2026-06-19) — Heal Analytics & Secret Scan

Two pure-stdlib audit/analysis tools. Full reference: [`docs/source/Eng/doc/new_features/v27_features_doc.rst`](docs/source/Eng/doc/new_features/v27_features_doc.rst).
Expand Down
7 changes: 7 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) — 流程文档(SOP)生成器](#本次更新-2026-06-19--流程文档sop生成器)
- [本次更新 (2026-06-19) — 修复分析与机密扫描](#本次更新-2026-06-19--修复分析与机密扫描)
- [本次更新 (2026-06-19) — CI 注解与剪贴板历史](#本次更新-2026-06-19--ci-注解与剪贴板历史)
- [本次更新 (2026-06-19) — 韧性原语](#本次更新-2026-06-19--韧性原语)
Expand Down Expand Up @@ -79,6 +80,12 @@

---

## 本次更新 (2026-06-19) — 流程文档(SOP)生成器

把动作列表转成逐步 SOP。完整参考:[`docs/source/Zh/doc/new_features/v28_features_doc.rst`](../docs/source/Zh/doc/new_features/v28_features_doc.rst)。

- **`generate_sop` / `write_sop`**(`AC_generate_sop`、`ac_generate_sop`):把录制/编写的动作列表映射成编号、人类可读步骤 + HTML 文档(UiPath Task-Capture 产出);内容 HTML 转义,未知指令优雅降级。

## 本次更新 (2026-06-19) — 修复分析与机密扫描

两项纯标准库的审计/分析工具。完整参考:[`docs/source/Zh/doc/new_features/v27_features_doc.rst`](../docs/source/Zh/doc/new_features/v27_features_doc.rst)。
Expand Down
7 changes: 7 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) — 流程文件(SOP)產生器](#本次更新-2026-06-19--流程文件sop產生器)
- [本次更新 (2026-06-19) — 修復分析與機密掃描](#本次更新-2026-06-19--修復分析與機密掃描)
- [本次更新 (2026-06-19) — CI 註解與剪貼簿歷史](#本次更新-2026-06-19--ci-註解與剪貼簿歷史)
- [本次更新 (2026-06-19) — 韌性原語](#本次更新-2026-06-19--韌性原語)
Expand Down Expand Up @@ -79,6 +80,12 @@

---

## 本次更新 (2026-06-19) — 流程文件(SOP)產生器

把動作清單轉成逐步 SOP。完整參考:[`docs/source/Zh/doc/new_features/v28_features_doc.rst`](../docs/source/Zh/doc/new_features/v28_features_doc.rst)。

- **`generate_sop` / `write_sop`**(`AC_generate_sop`、`ac_generate_sop`):把錄製/編寫的動作清單對應成編號、人類可讀步驟 + HTML 文件(UiPath Task-Capture 產出);內容 HTML 轉義,未知指令優雅降級。

## 本次更新 (2026-06-19) — 修復分析與機密掃描

兩項純標準庫的稽核/分析工具。完整參考:[`docs/source/Zh/doc/new_features/v27_features_doc.rst`](../docs/source/Zh/doc/new_features/v27_features_doc.rst)。
Expand Down
32 changes: 32 additions & 0 deletions docs/source/Eng/doc/new_features/v28_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
==================================================
New Features (2026-06-19) — Process-Doc (SOP) Generator
==================================================

Turn a recorded / authored action list into a numbered, human-readable
**standard operating procedure** — a structured step list plus an HTML
rendering (the UiPath Task-Capture deliverable) for runbooks and review.
Pure standard library; full stack.

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


Usage
=====

::

from je_auto_control import generate_sop, write_sop

doc = generate_sop(actions, title="Invoice Login")
doc["steps"] # [{n, command, description, args}, ...]
doc["html"] # full HTML document (content escaped)
write_sop(actions, "procedure.html", title="Invoice Login")

Each action is mapped to a human verb phrase (``AC_write`` → "Type text",
``AC_click_mouse`` → "Click the mouse", …) with the most descriptive
argument appended; unknown commands fall back to a readable form of the
name. User content is HTML-escaped. Exposed as ``AC_generate_sop`` /
``ac_generate_sop`` (writes a file when ``path`` is given, else returns the
structured document).
1 change: 1 addition & 0 deletions docs/source/Eng/eng_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Comprehensive guides for all AutoControl features.
doc/new_features/v25_features_doc
doc/new_features/v26_features_doc
doc/new_features/v27_features_doc
doc/new_features/v28_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
30 changes: 30 additions & 0 deletions docs/source/Zh/doc/new_features/v28_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
==================================================
新功能 (2026-06-19) — 流程文件(SOP)產生器
==================================================

把錄製 / 編寫的動作清單轉成編號、人類可讀的**標準作業程序(SOP)**——
結構化步驟清單加上 HTML 呈現(UiPath Task-Capture 的產出),供 runbook
與審閱使用。純標準庫;走完整五層。

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


用法
====

::

from je_auto_control import generate_sop, write_sop

doc = generate_sop(actions, title="Invoice Login")
doc["steps"] # [{n, command, description, args}, ...]
doc["html"] # 完整 HTML 文件(內容已轉義)
write_sop(actions, "procedure.html", title="Invoice Login")

每個動作會對應到人類動詞片語(``AC_write`` → 「Type text」、
``AC_click_mouse`` → 「Click the mouse」…),並附上最具描述性的引數;
未知指令則退回為可讀的名稱形式。使用者內容會做 HTML 轉義。對應
``AC_generate_sop`` / ``ac_generate_sop``(給 ``path`` 時寫檔,否則回傳
結構化文件)。
1 change: 1 addition & 0 deletions docs/source/Zh/zh_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ AutoControl 所有功能的完整使用指南。
doc/new_features/v25_features_doc
doc/new_features/v26_features_doc
doc/new_features/v27_features_doc
doc/new_features/v28_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 @@ -191,6 +191,10 @@
# Self-heal analytics + action-secrets scanning (audit/analysis)
from je_auto_control.utils.heal_analytics import analyze_heal_log, heal_stats
from je_auto_control.utils.secrets_scan import scan_secrets
# Process-documentation (SOP) generator from an action list
from je_auto_control.utils.process_doc import (
describe_step, generate_sop, write_sop,
)
# Background popup/interrupt watchdog (unattended automation)
from je_auto_control.utils.watchdog import (
PopupWatchdog, WatchdogRule, default_popup_watchdog,
Expand Down Expand Up @@ -623,6 +627,7 @@ def start_autocontrol_gui(*args, **kwargs):
"emit_annotations", "format_annotation",
"ClipboardHistory", "default_clipboard_history",
"analyze_heal_log", "heal_stats", "scan_secrets",
"describe_step", "generate_sop", "write_sop",
# MCP server
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",
Expand Down
10 changes: 10 additions & 0 deletions je_auto_control/gui/script_builder/command_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,16 @@ def _add_misc_specs(specs: List[CommandSpec]) -> None:
_add_resilience_specs(specs)
_add_devex_specs(specs)
_add_audit_specs(specs)
specs.append(CommandSpec(
"AC_generate_sop", "Report", "Generate SOP Document",
fields=(
FieldSpec("title", FieldType.STRING, optional=True,
default="Automation Procedure"),
FieldSpec("path", FieldType.FILE_PATH, optional=True),
),
description="Build a step-by-step SOP (HTML) from 'actions' (JSON "
"view).",
))


def _add_audit_specs(specs: List[CommandSpec]) -> None:
Expand Down
10 changes: 10 additions & 0 deletions je_auto_control/utils/executor/action_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2863,6 +2863,15 @@ def _scan_secrets(data: Any) -> Dict[str, Any]:
return {"findings": scan_secrets(data)}


def _generate_sop(actions: List[Any], title: str = "Automation Procedure",
path: Optional[str] = None) -> Dict[str, Any]:
"""Adapter: build (or write) a step-by-step SOP from an action list."""
from je_auto_control.utils.process_doc import generate_sop, write_sop
if path:
return {"path": write_sop(actions, path, title=title)}
return generate_sop(actions, title=title)


class Executor:
"""
Executor
Expand Down Expand Up @@ -3090,6 +3099,7 @@ def __init__(self):
"AC_clip_history_stop": _clip_history_stop,
"AC_heal_stats": _heal_stats,
"AC_scan_secrets": _scan_secrets,
"AC_generate_sop": _generate_sop,
"AC_a11y_record_start": _a11y_record_start,
"AC_a11y_record_stop": _a11y_record_stop,
"AC_a11y_record_events": _a11y_record_events,
Expand Down
20 changes: 20 additions & 0 deletions je_auto_control/utils/mcp_server/tools/_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -2502,6 +2502,25 @@ def audit_analysis_tools() -> List[MCPTool]:
]


def process_doc_tools() -> List[MCPTool]:
return [
MCPTool(
name="ac_generate_sop",
description=("Generate a step-by-step SOP document from an action "
"list (numbered steps + HTML, the Task-Capture "
"deliverable). Writes to 'path' when given, else "
"returns the structured doc."),
input_schema=schema({
"actions": {"type": "array"},
"title": {"type": "string"},
"path": {"type": "string"}},
required=["actions"]),
handler=h.generate_sop,
annotations=SIDE_EFFECT_ONLY,
),
]


def unattended_tools() -> List[MCPTool]:
return [
MCPTool(
Expand Down Expand Up @@ -3559,6 +3578,7 @@ def media_assert_tools() -> List[MCPTool]:
checkpoint_tools, set_of_marks_tools, screen_state_tools,
input_macro_tools, resilience_tools,
ci_annotation_tools, clipboard_history_tools, audit_analysis_tools,
process_doc_tools,
screen_record_tools,
process_and_shell_tools, remote_desktop_tools, gamepad_tools,
usb_passthrough_tools, assertion_tools, data_source_tools,
Expand Down
8 changes: 8 additions & 0 deletions je_auto_control/utils/mcp_server/tools/_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,14 @@ def scan_secrets(data):
return {"findings": _scan(data)}


def generate_sop(actions, title="Automation Procedure", path=None):
from je_auto_control.utils.process_doc import generate_sop as _gen
from je_auto_control.utils.process_doc import write_sop as _write
if path:
return {"path": _write(actions, path, title=title)}
return _gen(actions, title=title)


def vlm_locate(description: str,
screen_region: Optional[List[int]] = None,
model: Optional[str] = None) -> Optional[List[int]]:
Expand Down
6 changes: 6 additions & 0 deletions je_auto_control/utils/process_doc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Generate a step-by-step SOP document from a recorded action list."""
from je_auto_control.utils.process_doc.process_doc import (
describe_step, generate_sop, write_sop,
)

__all__ = ["describe_step", "generate_sop", "write_sop"]
79 changes: 79 additions & 0 deletions je_auto_control/utils/process_doc/process_doc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Generate a step-by-step SOP document from an action list.

AutoControl records actions but doesn't *document* them. This turns a
recorded / authored action list into a numbered, human-readable
standard-operating-procedure — a structured step list plus an HTML
rendering (the UiPath Task-Capture deliverable) — for runbooks and review.

Pure standard library (``html`` escaping); imports no ``PySide6``.
"""
import html
from pathlib import Path
from typing import Any, Dict, List

# Command -> human verb phrase.
_VERBS = {
"AC_click_mouse": "Click the mouse",
"AC_press_mouse": "Press the mouse button",
"AC_release_mouse": "Release the mouse button",
"AC_set_mouse_position": "Move the mouse",
"AC_mouse_scroll": "Scroll the wheel",
"AC_type_keyboard": "Press a key",
"AC_press_keyboard_key": "Hold a key",
"AC_release_keyboard_key": "Release a key",
"AC_write": "Type text",
"AC_hotkey": "Press a hotkey",
"AC_screenshot": "Take a screenshot",
"AC_locate_and_click": "Find an image and click it",
"AC_locate_image_center": "Locate an image",
"AC_click_text": "Click on text",
"AC_set_var": "Set a variable",
}
# Per-command arg whose value is the most descriptive detail.
_DETAIL_KEYS = ("write_string", "text", "keycode", "key_code_list", "image",
"name", "value", "x")


def describe_step(command: str, args: Dict[str, Any]) -> str:
"""Return a human-readable description for one action."""
base = _VERBS.get(command,
command.replace("AC_", "").replace("_", " ").strip()
or "Action")
for key in _DETAIL_KEYS:
if key in args and args[key] not in (None, ""):
return f"{base} ({key}: {args[key]})"
return base


def generate_sop(actions: List[Any], *,
title: str = "Automation Procedure") -> Dict[str, Any]:
"""Return a structured SOP for ``actions`` plus an HTML rendering."""
steps: List[Dict[str, Any]] = []
for index, action in enumerate(actions, start=1):
command = action[0] if action and isinstance(action[0], str) else "?"
args = (action[1] if len(action) > 1 and isinstance(action[1], dict)
else {})
steps.append({"n": index, "command": command,
"description": describe_step(command, args),
"args": args})
return {"title": title, "step_count": len(steps), "steps": steps,
"html": _render_html(title, steps)}


def _render_html(title: str, steps: List[Dict[str, Any]]) -> str:
items = "\n".join(
f" <li><strong>Step {step['n']}:</strong> "
f"{html.escape(step['description'])}</li>" for step in steps)
return (
"<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\">"
f"<title>{html.escape(title)}</title></head>\n<body>\n"
f"<h1>{html.escape(title)}</h1>\n<ol>\n{items}\n</ol>\n</body></html>\n")


def write_sop(actions: List[Any], path: str, *,
title: str = "Automation Procedure") -> str:
"""Write the SOP HTML for ``actions`` to ``path``; return the path."""
document = generate_sop(actions, title=title)
target = Path(path)
target.write_text(document["html"], encoding="utf-8")
return str(target.resolve())
53 changes: 53 additions & 0 deletions test/unit_test/headless/test_process_doc_batch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Headless tests for the process-doc (SOP) generator. Pure stdlib."""
import je_auto_control as ac
from je_auto_control.utils.process_doc import (
describe_step, generate_sop, write_sop)


def test_describe_step_uses_verb_and_detail():
assert describe_step("AC_write", {"write_string": "hello"}) == \
"Type text (write_string: hello)"
assert describe_step("AC_click_mouse", {}) == "Click the mouse"
assert describe_step("AC_custom_thing", {}) == "custom thing"


def test_generate_sop_structure_and_html():
actions = [
["AC_set_mouse_position", {"x": 10, "y": 20}],
["AC_click_mouse", {}],
["AC_write", {"write_string": "<b>hi</b>"}],
]
doc = generate_sop(actions, title="Login Flow")
assert doc["title"] == "Login Flow" and doc["step_count"] == 3
assert [s["n"] for s in doc["steps"]] == [1, 2, 3]
assert doc["steps"][1]["description"] == "Click the mouse"
# HTML escapes user content and is well-formed
assert "&lt;b&gt;hi&lt;/b&gt;" in doc["html"]
assert doc["html"].startswith("<!doctype html>")
assert "<h1>Login Flow</h1>" in doc["html"]


def test_write_sop_writes_file(tmp_path):
path = write_sop([["AC_click_mouse", {}]], str(tmp_path / "sop.html"),
title="P")
assert open(path, encoding="utf-8").read().count("<li>") == 1


# --- wiring ---------------------------------------------------------------

def test_executor_wiring(tmp_path):
rec = ac.execute_action([["AC_generate_sop", {
"actions": [["AC_click_mouse", {}]], "title": "T"}]])
assert any("step_count" in str(v) for v in rec.values())
assert "AC_generate_sop" in ac.executor.known_commands()
from je_auto_control.utils.mcp_server.tools import (
build_default_tool_registry)
assert "ac_generate_sop" in {t.name for t in build_default_tool_registry()}
from je_auto_control.gui.script_builder.command_schema import _build_specs
assert "AC_generate_sop" in {s.command for s in _build_specs()}


def test_facade_exports():
for attr in ("describe_step", "generate_sop", "write_sop"):
assert hasattr(ac, attr)
assert attr in ac.__all__
Loading