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) — 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)
- [What's new (2026-06-19) — Timed Input Macros](#whats-new-2026-06-19--timed-input-macros)
- [What's new (2026-06-19) — Semantic Screen State](#whats-new-2026-06-19--semantic-screen-state)
Expand Down Expand Up @@ -78,6 +79,13 @@

---

## What's new (2026-06-19) — CI Annotations & Clipboard History

Two pure-stdlib utilities. Full reference: [`docs/source/Eng/doc/new_features/v26_features_doc.rst`](docs/source/Eng/doc/new_features/v26_features_doc.rst).

- **CI annotations** — `emit_annotations(results)` (`AC_ci_annotations`, `ac_ci_annotations`): turn result dicts into GitHub Actions workflow commands (`::error file=...,line=...::msg`) so failures show inline in a PR, no reporter action needed.
- **Clipboard history** — `ClipboardHistory` / `default_clipboard_history` (`AC_clip_history_capture`/`list`/`search`/`start`/`stop`, `ac_clip_history_*`): a capped, searchable, newest-first ring buffer of copied text with an optional background poller.

## What's new (2026-06-19) — Resilience Primitives

Reusable retry + circuit-breaker primitives. Full reference: [`docs/source/Eng/doc/new_features/v25_features_doc.rst`](docs/source/Eng/doc/new_features/v25_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) — CI 注解与剪贴板历史](#本次更新-2026-06-19--ci-注解与剪贴板历史)
- [本次更新 (2026-06-19) — 韧性原语](#本次更新-2026-06-19--韧性原语)
- [本次更新 (2026-06-19) — 计时输入宏](#本次更新-2026-06-19--计时输入宏)
- [本次更新 (2026-06-19) — 语义屏幕状态](#本次更新-2026-06-19--语义屏幕状态)
Expand Down Expand Up @@ -77,6 +78,13 @@

---

## 本次更新 (2026-06-19) — CI 注解与剪贴板历史

两项纯标准库工具。完整参考:[`docs/source/Zh/doc/new_features/v26_features_doc.rst`](../docs/source/Zh/doc/new_features/v26_features_doc.rst)。

- **CI 注解** — `emit_annotations(results)`(`AC_ci_annotations`、`ac_ci_annotations`):把结果 dict 转成 GitHub Actions 工作流命令(`::error file=...,line=...::msg`),让失败在 PR 行内显示,免 reporter action。
- **剪贴板历史** — `ClipboardHistory` / `default_clipboard_history`(`AC_clip_history_capture`/`list`/`search`/`start`/`stop`、`ac_clip_history_*`):有上限、可搜索、最新在前的复制文本环形缓冲,含可选后台轮询器。

## 本次更新 (2026-06-19) — 韧性原语

可重用的 retry 与断路器原语。完整参考:[`docs/source/Zh/doc/new_features/v25_features_doc.rst`](../docs/source/Zh/doc/new_features/v25_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) — CI 註解與剪貼簿歷史](#本次更新-2026-06-19--ci-註解與剪貼簿歷史)
- [本次更新 (2026-06-19) — 韌性原語](#本次更新-2026-06-19--韌性原語)
- [本次更新 (2026-06-19) — 計時輸入巨集](#本次更新-2026-06-19--計時輸入巨集)
- [本次更新 (2026-06-19) — 語意螢幕狀態](#本次更新-2026-06-19--語意螢幕狀態)
Expand Down Expand Up @@ -77,6 +78,13 @@

---

## 本次更新 (2026-06-19) — CI 註解與剪貼簿歷史

兩項純標準庫工具。完整參考:[`docs/source/Zh/doc/new_features/v26_features_doc.rst`](../docs/source/Zh/doc/new_features/v26_features_doc.rst)。

- **CI 註解** — `emit_annotations(results)`(`AC_ci_annotations`、`ac_ci_annotations`):把結果 dict 轉成 GitHub Actions 工作流程命令(`::error file=...,line=...::msg`),讓失敗在 PR 行內顯示,免 reporter action。
- **剪貼簿歷史** — `ClipboardHistory` / `default_clipboard_history`(`AC_clip_history_capture`/`list`/`search`/`start`/`stop`、`ac_clip_history_*`):有上限、可搜尋、最新在前的複製文字環狀緩衝,含可選背景輪詢器。

## 本次更新 (2026-06-19) — 韌性原語

可重用的 retry 與斷路器原語。完整參考:[`docs/source/Zh/doc/new_features/v25_features_doc.rst`](../docs/source/Zh/doc/new_features/v25_features_doc.rst)。
Expand Down
49 changes: 49 additions & 0 deletions docs/source/Eng/doc/new_features/v26_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
==================================================
New Features (2026-06-19) — CI Annotations & Clipboard History
==================================================

Two pure-standard-library utilities: emit CI annotations from results, and
keep a searchable clipboard history. Full stack.

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


CI workflow annotations
======================

::

from je_auto_control import emit_annotations

emit_annotations([
{"level": "error", "message": "step failed",
"file": "flows/login.json", "line": 12, "title": "Login"},
])
# prints: ::error file=flows/login.json,line=12,title=Login::step failed

Converts result dicts (``{level, message, file?, line?, col?, title?}``)
into GitHub Actions workflow commands so failures surface **inline** in a PR
— no third-party reporter action required. ``level`` is ``error`` /
``warning`` / ``notice``; values are escaped per GitHub's rules. Exposed as
``AC_ci_annotations`` / ``ac_ci_annotations``.


Clipboard history
================

::

from je_auto_control import ClipboardHistory, default_clipboard_history

default_clipboard_history.start() # poll the clipboard in the background
default_clipboard_history.search("invoice")
default_clipboard_history.get(0) # most recent entry

A capped, newest-first ring buffer of distinct clipboard text entries with
``add`` / ``snapshot`` / ``get`` / ``search`` / ``clear`` and an optional
background poller (``start`` / ``stop`` / ``capture_once``). Exposed as
``AC_clip_history_capture`` / ``AC_clip_history_list`` /
``AC_clip_history_search`` / ``AC_clip_history_start`` /
``AC_clip_history_stop`` (and ``ac_clip_history_*``).
1 change: 1 addition & 0 deletions docs/source/Eng/eng_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Comprehensive guides for all AutoControl features.
doc/new_features/v23_features_doc
doc/new_features/v24_features_doc
doc/new_features/v25_features_doc
doc/new_features/v26_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
47 changes: 47 additions & 0 deletions docs/source/Zh/doc/new_features/v26_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
==================================================
新功能 (2026-06-19) — CI 註解與剪貼簿歷史
==================================================

兩項純標準庫工具:從結果輸出 CI 註解,以及保存可搜尋的剪貼簿歷史。
走完整五層。

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


CI 工作流程註解
===============

::

from je_auto_control import emit_annotations

emit_annotations([
{"level": "error", "message": "step failed",
"file": "flows/login.json", "line": 12, "title": "Login"},
])
# 印出:::error file=flows/login.json,line=12,title=Login::step failed

把結果 dict(``{level, message, file?, line?, col?, title?}``)轉成 GitHub
Actions 工作流程命令,讓失敗在 PR 中**行內**顯示——不需第三方 reporter
action。``level`` 為 ``error`` / ``warning`` / ``notice``;值會依 GitHub
規則轉義。對應 ``AC_ci_annotations`` / ``ac_ci_annotations``。


剪貼簿歷史
==========

::

from je_auto_control import ClipboardHistory, default_clipboard_history

default_clipboard_history.start() # 背景輪詢剪貼簿
default_clipboard_history.search("invoice")
default_clipboard_history.get(0) # 最近一筆

一個有上限、最新在前、去重的剪貼簿文字環狀緩衝,具 ``add`` / ``snapshot``
/ ``get`` / ``search`` / ``clear`` 及可選的背景輪詢器(``start`` / ``stop``
/ ``capture_once``)。對應 ``AC_clip_history_capture`` /
``AC_clip_history_list`` / ``AC_clip_history_search`` /
``AC_clip_history_start`` / ``AC_clip_history_stop``(以及 ``ac_clip_history_*``)。
1 change: 1 addition & 0 deletions docs/source/Zh/zh_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ AutoControl 所有功能的完整使用指南。
doc/new_features/v23_features_doc
doc/new_features/v24_features_doc
doc/new_features/v25_features_doc
doc/new_features/v26_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
10 changes: 10 additions & 0 deletions je_auto_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,14 @@
from je_auto_control.utils.resilience import (
CircuitBreaker, CircuitOpenError, RetryPolicy, retry_call,
)
# CI workflow annotations (GitHub Actions)
from je_auto_control.utils.ci_annotations import (
emit_annotations, format_annotation,
)
# Clipboard history (ring buffer + background poller)
from je_auto_control.utils.clipboard_history import (
ClipboardHistory, default_clipboard_history,
)
# Background popup/interrupt watchdog (unattended automation)
from je_auto_control.utils.watchdog import (
PopupWatchdog, WatchdogRule, default_popup_watchdog,
Expand Down Expand Up @@ -609,6 +617,8 @@ def start_autocontrol_gui(*args, **kwargs):
"snapshot_screen",
"replay_timeline", "run_sequence",
"CircuitBreaker", "CircuitOpenError", "RetryPolicy", "retry_call",
"emit_annotations", "format_annotation",
"ClipboardHistory", "default_clipboard_history",
# MCP server
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",
Expand Down
20 changes: 20 additions & 0 deletions je_auto_control/gui/script_builder/command_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,26 @@ def _add_misc_specs(specs: List[CommandSpec]) -> None:
_add_screen_state_specs(specs)
_add_input_macro_specs(specs)
_add_resilience_specs(specs)
_add_devex_specs(specs)


def _add_devex_specs(specs: List[CommandSpec]) -> None:
specs.append(CommandSpec(
"AC_ci_annotations", "Tools", "Emit CI Annotations",
description="Emit GitHub Actions annotations from 'annotations' "
"(JSON view).",
))
specs.append(CommandSpec(
"AC_clip_history_capture", "Misc", "Clipboard History: Capture"))
specs.append(CommandSpec(
"AC_clip_history_list", "Misc", "Clipboard History: List"))
specs.append(CommandSpec(
"AC_clip_history_search", "Misc", "Clipboard History: Search",
fields=(FieldSpec("query", FieldType.STRING),)))
specs.append(CommandSpec(
"AC_clip_history_start", "Misc", "Clipboard History: Start"))
specs.append(CommandSpec(
"AC_clip_history_stop", "Misc", "Clipboard History: Stop"))


def _add_resilience_specs(specs: List[CommandSpec]) -> None:
Expand Down
6 changes: 6 additions & 0 deletions je_auto_control/utils/ci_annotations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Emit CI workflow annotations (GitHub Actions) from run results."""
from je_auto_control.utils.ci_annotations.ci_annotations import (
emit_annotations, format_annotation,
)

__all__ = ["emit_annotations", "format_annotation"]
56 changes: 56 additions & 0 deletions je_auto_control/utils/ci_annotations/ci_annotations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Emit CI workflow annotations from run results (GitHub Actions format).

A JUnit file needs a third-party reporter action to surface failures in a
PR. Emitting GitHub Actions *workflow commands* (``::error file=...,line=...,
title=...::message``) prints failures as inline annotations with zero extra
config. This converts a list of result dicts into those lines.

Pure standard library; imports no ``PySide6``.
"""
import sys
from typing import Any, Dict, List, Optional, TextIO

_LEVELS = {"error", "warning", "notice"}


def _escape(value: str) -> str:
"""Escape a workflow-command property value per GitHub's rules."""
return (str(value).replace("%", "%25").replace("\r", "%0D")
.replace("\n", "%0A").replace(":", "%3A").replace(",", "%2C"))


def _escape_message(value: str) -> str:
return str(value).replace("%", "%25").replace("\r", "%0D").replace(
"\n", "%0A")


def format_annotation(annotation: Dict[str, Any]) -> str:
"""Format one annotation as a GitHub Actions workflow command.

``{level, message, file?, line?, col?, title?}``; ``level`` is
``error`` / ``warning`` / ``notice`` (defaults to ``error``).
"""
level = str(annotation.get("level", "error")).lower()
if level not in _LEVELS:
level = "error"
props = []
for key, prop in (("file", "file"), ("line", "line"), ("col", "col"),
("title", "title")):
value = annotation.get(key)
if value not in (None, ""):
props.append(f"{prop}={_escape(value)}")
prefix = f"::{level} " + ",".join(props) if props else f"::{level}"
return f"{prefix}::{_escape_message(annotation.get('message', ''))}"


def emit_annotations(annotations: List[Dict[str, Any]], *,
stream: Optional[TextIO] = None) -> List[str]:
"""Format each annotation and write the lines to ``stream`` (stdout).

Returns the formatted lines (also useful for tests / piping).
"""
out = stream if stream is not None else sys.stdout
lines = [format_annotation(item) for item in annotations]
for line in lines:
out.write(line + "\n")
return lines
6 changes: 6 additions & 0 deletions je_auto_control/utils/clipboard_history/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Clipboard history: a ring buffer + background poller over the clipboard."""
from je_auto_control.utils.clipboard_history.clipboard_history import (
ClipboardHistory, default_clipboard_history,
)

__all__ = ["ClipboardHistory", "default_clipboard_history"]
Loading
Loading