diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 000000000..f0699d47e --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,145 @@ +name: AutoControl Dev CI + +# The dev channel. A push or pull request to dev runs the headless suite, and +# a push that passes is then built from dev.toml and published to PyPI as +# je_auto_control_dev (the publish-dev job). main has its own workflows: +# quality.yml gates it and stable.yml publishes je_auto_control. + +on: + push: + branches: [ "dev" ] + pull_request: + branches: [ "dev" ] + +permissions: + contents: read + +jobs: + pytest-headless: + # The same suite, install steps and command as the pytest-headless job of + # quality.yml, without the coverage floor: that ratchet belongs to main. + # Every square here is one quality.yml also runs. Each operating system + # runs once, because the facade imports a different backend on each, and + # Windows runs both ends of the supported Python range. The full nine-way + # matrix runs on the pull request that takes dev to main. + runs-on: ${{ matrix.os }} + timeout-minutes: 30 # about 3x a square of the same suite in quality.yml + strategy: + fail-fast: false + matrix: + include: + - { os: windows-2022, python-version: "3.10" } + - { os: windows-2022, python-version: "3.14" } + - { os: ubuntu-22.04, python-version: "3.14" } + - { os: macos-14, python-version: "3.10" } + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + + # The X11 backend connects to a display at import time, opencv and + # PySide6 need libGL/glib, and Qt's platform plugin needs the xcb + # libraries: without them the suite fails at collection. + - name: Install X11 and Qt runtime libraries (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + xvfb xauth x11-utils \ + libgl1 libegl1 libglib2.0-0 \ + libxkbcommon-x11-0 libdbus-1-3 \ + libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \ + libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-sync1 \ + libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 + + # One command per step, each on a single line: a `run: |` block scalar + # swallows NOSONAR markers. + - name: Upgrade the installer + shell: bash + run: python -m pip install --upgrade pip wheel # NOSONAR githubactions:S8544 # reason: pip and wheel are the installer; pinning them here would pin the tool that applies the pins below + + # The editable project, not dev_requirements.txt: that file installs the + # published je_auto_control_dev, whose copy of je_auto_control/ in + # site-packages would be tested instead of this checkout. The WebRTC + # extra is there because the tests of the WebRTC host skip without it. + - name: Install the project itself, with the WebRTC extra + shell: bash + run: pip install -e ".[webrtc]" # NOSONAR githubactions:S8544 githubactions:S8541 # reason: installs the checked-out project itself, so there is no upstream version to lock and no third-party setup script to run + + - name: Install the test tooling + shell: bash + # The same line as quality.yml (test_dev_release.py compares them). + # Quoted: `--only-binary :all:` puts a colon-space inside the scalar. + run: "pip install --only-binary :all: ruff==0.15.22 bandit==1.9.4 pytest==9.1.1 pytest-timeout==2.4.0 pytest-rerunfailures==15.1 coverage==7.15.4 PySide6==6.11.1 radon==6.0.1" + + # No path argument: the paths come from `testpaths` in pyproject.toml, + # and an argument would drop the flow_control tests. Linux runs under a + # real Xvfb because the X11 backend opens a display at import time. + - name: Run headless pytest suite + shell: bash + run: >- + ${{ runner.os == 'Linux' && 'xvfb-run -a -s "-screen 0 1280x800x24"' || '' }} + python -m pytest -v --tb=short --timeout=120 + + publish-dev: + # A push to dev that passes the tests is built from dev.toml and uploaded + # when it is still the tip of dev and ships something the newest + # je_auto_control_dev does not. scripts/dev_release.py picks the version + # from PyPI, so nothing is committed back. The condition keeps the job off + # pull requests. + name: Publish je_auto_control_dev to PyPI + needs: [pytest-headless] + if: github.event_name == 'push' && github.ref == 'refs/heads/dev' + runs-on: ubuntu-latest + timeout-minutes: 15 + concurrency: + group: publish-dev + cancel-in-progress: false + + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install build tooling + run: "pip install --only-binary :all: build==1.5.0 twine==6.2.0" + + - name: Write pyproject.toml from dev.toml with the next version + run: python scripts/dev_release.py prepare + + - name: Build distribution + run: python -m build + + - name: Verify distribution metadata + run: python -m twine check dist/* + + - name: Compare with the newest published wheel + id: compare + run: python scripts/dev_release.py changed dist + + # A run that finishes after a newer push would otherwise publish older + # code as the newest release. + - name: Check that this commit is still the tip of dev + id: tip + run: | + tip="$(git ls-remote origin refs/heads/dev | cut -f1)" + if [ "$tip" = "$GITHUB_SHA" ]; then current=true; else current=false; fi + echo "current=$current" >> "$GITHUB_OUTPUT" + + - name: Publish to PyPI + if: steps.compare.outputs.changed == 'true' && steps.tip.outputs.current == 'true' + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: python -m twine upload --non-interactive dist/* diff --git a/CHANGELOG.md b/CHANGELOG.md index a9a8e1b8e..f5fe83ee6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,33 @@ it shipped into a `## [x.y.z] - date` section of their own; the tag's ### Added +- `write_secret(secret)` / `AC_write_secret` (`secret`): type a password or + token as Unicode key events without logging, recording or returning it; an + error never names a character. Refuses on a backend without Unicode typing. +- A package gate in front of `AC_add_package_to_executor` and + `AC_add_package_to_callback_executor`: `executor.allow_packages(*names)` (submodules + included) and `executor.set_allow_arbitrary_packages(enabled)` (also on + `package_manager`). A refused package raises `AutoControlExecuteActionException` + before it is imported. +- `message_format.MessageFormatError` (an `AutoControlException` and a + `ValueError`); locales other than en/fr use Babel's CLDR plural rules when + Babel is installed. +- `multipart.MultipartError` (an `AutoControlException` and a `ValueError`). +- `dotenv.DotenvError`, raised by `dump_dotenv` for a key the parser would not + read back. +- `HotkeyDaemon.is_running`, `Scheduler.is_running` and + `TriggerEngine.is_running`: whether the engine's thread is alive. +- `usb_watcher.hold_default_watcher()` / `release_default_watcher()`: share + the default USB hotplug watcher by count. +- `rate_limit.named_bucket(name, rate, capacity)`, the named token buckets + `AC_rate_limit` and `ac_rate_limit` share. +- `box_format` (`"xywh"` / `"ltrb"`) on `cluster_grid`, `locate_cell`, + `AC_grid_cell` and `ac_grid_cell`, so `locate_all_image` boxes address cells. +- `repair_store(db=None)`: the repair store in `db`, or the one the process + shares. +- `HistoryStore.list_runs(statuses=...)` and `FINISHED_STATUSES`. +- `element_box()` (`utils.accessibility.element`): an element's + `(left, top, width, height)` from `bbox`, `bounds` or `x/y/width/height`. - The MCP server speaks the stateless protocol revision 2026-07-28 over stdio and HTTP, per request, beside the `initialize`-based ones: `server/discover`, `resultType` and caching hints on results, the `-32020`–`-32022` error @@ -82,8 +109,69 @@ it shipped into a `## [x.y.z] - date` section of their own; the tag's generating values from the schema alone used to produce a plain string and get a `ValueError` out of `datetime.fromisoformat`. +### Deprecated + +- Loading a package that is not on the allowlist while the package gate is unconfigured. + It still works, with a `DeprecationWarning`; a future release will refuse it by default. + Migration: call `executor.allow_packages(...)` for the packages your action lists load, + or `executor.set_allow_arbitrary_packages(True)` to keep loading any package. + ### Changed +- `je_auto_control_dev`, the dev-channel package, declares what + `je_auto_control` declares: the same pinned dependencies and platform + markers (`defusedxml`, `cryptography` and the `opencv-python` bound are new + to it), all ten extras instead of `gui` alone, the `je_auto_control` and + `je_auto_control_mcp` commands, the pytest plugin entry point, `py.typed` + and the remote desktop web viewer files. CI publishes it from the `dev` + branch. +- `format_message` raises `MessageFormatError` for patterns ICU rejects + (unterminated argument, selector without `{...}`, no `other`, duplicate + selector, late `offset:`), and an unsupported locale raises instead of + getting English rules. +- `occurrences(count=, until=)` narrows the rule's own COUNT / UNTIL instead of + replacing it, and `parse_rrule` refuses `BYMONTHDAY` with `WEEKLY`, a numbered + `BYDAY` with `DAILY` / `WEEKLY`, and an unknown `WKST` (RFC 5545 3.3.10). +- `decode_jwt` / `AC_jwt_decode` refuse a token that carries `aud` unless the + policy's `audience` names one of its values (RFC 7519 4.1.3); pass the + audience to keep accepting such tokens. +- The JSON document MCP tools accept a root array as well as an object. +- Malformed JSON-RPC envelopes are answered `-32600`; `"id": null` is a + request, answered with `"id": null`. +- `evaluate_trajectory` raises `ValueError` for a rubric that is not an + object or has unknown keys; `scale_sweep` / `detect_scale` reject + non-finite or non-positive scales; `rbac.UserRecord` is frozen. +- `match_subpixel` reports `cx` / `cy` as the pixel-index centre plus the + fitted offset (half a pixel less than before). +- `RetryBudget` validates `jitter` (case-insensitive), `base_delay_s` and + `max_delay_s`; `AC_plan_retry_delays` accepts at most 10,000 attempts. +- Asset types refuse values they cannot hold (`3.7` or `True` as an `int`, + `"enabled"` as a `bool`); element locators refuse blank filters. +- Skills nested deeper than 50 fail the outermost `AC_skill_run`. +- `is_interactive_role` and `flatten_tree` recognise AT-SPI, macOS AX and ARIA + role names as well as UIA; `tab_order` leaves out disabled controls. +- `effect_near_point` / `classify_effect` measure `radius` as a circle. +- `diff_rows` / `cell_changes` raise `ValueError` for an empty key or a row + without a key column; `score_step_rule_based` raises for an unknown effect; + `verify_totp` raises `TOTPError` for a negative window. +- `RenewalScheduler` rejects a `check_interval_s` that is not finite and + positive; `build_timeline` / `critical_steps` reject NaN, infinite and + negative durations; `verify_artifact` stores non-text values as JSON. +- `find_color_regions`, `segment_hsv` and `dominant_hue_regions` return + screen coordinates for blobs found in a grabbed `region` (they were + relative to the region's corner); a supplied `haystack` keeps its pixels. +- `psi` / `ks_two_sample` / `detect_drift` raise `ValueError` for NaN. +- `profile_rows` reports `mixed` for a column with no single type, and + `infer_schema` gives it no `type` rule; int columns have exact bounds. +- `diff_runs` compares a 0 s step as 0.1 s (`ratio` `None`) and skips + non-finite durations. +- `find_repeated_sequences` / `mine_action_log` and `Baggage` refuse empty + lengths, counts and keys with `ValueError`. +- `validate_config` / `ConfigSchema.from_dict` raise `ValueError` for an + unknown field type, and a `str` field rejects `None` and containers. +- Search terms: runs of kana, CJK ideographs and Hangul are indexed as + character bigrams, so a word is found inside a sentence. `mode="tfidf"` + uses a smoothed IDF, so a term in every document still matches. - An MCP HTTP request whose `MCP-Protocol-Version` header names an unsupported version is still a 400, now with a JSON-RPC `UnsupportedProtocolVersion` (`-32022`) body listing the supported @@ -323,6 +411,18 @@ it shipped into a `## [x.y.z] - date` section of their own; the tag's ### Security +- `ac_egress_reset` and `ac_approve_artifact` are destructive, so the + confirmation gate asks before loosening egress or overwriting an approved + baseline. +- The rbac user store saves a change before applying it, so a refused or + failed save no longer takes effect in memory, and it hands out copies of + its records. +- Failure bundles mask secrets inside nested mappings, tuple actions and + objects serialised through `repr`. +- `AC_resolve_ref` / `AC_resolve_refs` and the MCP tools `ac_resolve_ref` / + `ac_resolve_refs` refuse `secret://` references: the resolved secret was + returned into executor records and MCP results. Use `${secrets.NAME}` in + the step that needs the value, or `resolve_ref` from Python. - The egress policy matches hosts by their IDNA encoding, so soft hyphens, fullwidth characters and ideographic full stops no longer slip past a deny list. @@ -427,6 +527,173 @@ it shipped into a `## [x.y.z] - date` section of their own; the tag's ### Fixed +- `AC_web_run` in its documented keyword form + (`{"action": "WR_to_url", "params": {...}}`) runs the command; it failed with + "action name must start with WR_". A failing `WR_*` command now raises + `WebRunnerBridgeError`, so the executor records it and runs the next action + instead of aborting the script, and the command runs through WebRunner's + `execute_one` (its command gates, retry policy and failure screenshots) when + the installed WebRunner has it. `is_webrunner_available()` no longer imports + `je_web_runner`, which wrote `WEBRunner.log` into the current directory. +- French `selectordinal` follows CLDR (`21e`, not `21er`), `fr_FR` / `fr-CA` + use the French rules, and `=1.0` matches 1. +- `attributes_to_otlp` / `spans_to_otlp` write a `bytes` attribute as OTLP + `bytesValue` (base64), and `is_problem` / `parse_problem` match the + `application/problem+json` media type exactly instead of as a substring. +- RRULE: a yearly `BYDAY` ordinal counts within the year when `BYMONTHDAY` is + present and `BYMONTH` is not, DTSTART with microseconds stays the first + occurrence, DAILY applies `BYSETPOS`, long intervals and large counts are no + longer cut short, and year 9999, a malformed `UNTIL` or a naive `now` no + longer raise builtin errors. +- `GettextCatalog.compile_mo` / `to_mo_bytes` leave untranslated entries out + of the `.mo`, as `msgfmt` does, so readers fall back to the `msgid` instead + of showing an empty string. +- `build_multipart` refuses a boundary RFC 2046 does not allow or that a part + contains (a field value could inject a part), redraws a generated one, and + `parse_multipart` reads only the `boundary` parameter. +- `SSEParser.feed` no longer re-splits the whole partial line on every chunk: + a 2 MB `data` line in 1 KB chunks took 10.75 s and now takes 0.02 s. +- `parse_dotenv` keeps a value that starts with `#` (`COLOR=#ff0000`), keeps + the trailing whitespace of a multi-line quoted value's lines, skips a leading + BOM, and parses an unclosed quote in linear time (20k lines took 190 s). +- W3C trace context: a tracestate value keeps its leading spaces, a member that + breaks the value grammar (including a CR LF) is discarded on parse and refused + on format, a 16k-member header parses in linear time, `format_traceparent` + validates a hand-built context, and `child_context` clears unknown flag bits. +- `parse_link_header` / `next_url` follow RFC 8288 Appendix B: a `<` in an + unquoted parameter value no longer swallows the next link, a valueless + parameter is kept as `""`, and relations split on space and tab only. +- `CookieJar`: `Expires` follows the RFC 6265 5.1.1 date algorithm (an + out-of-range year no longer raises `OverflowError`), the last valid `Max-Age` + or `Expires` decides, `Max-Age` takes ASCII digits only, and a `Set-Cookie` + with a control character is ignored instead of reaching the `Cookie` header. +- `normalize_url` / `canonicalize_url` resolve a percent-encoded `..`, never + turn a path that begins with `//` into a host, normalise the query before + sorting it, and keep an empty `?` or `#` (RFC 3986 6.2.2, 3.3, 6.2.3). +- JWT: an empty or non-string key raises `JwtError` instead of signing and + verifying (or raising `TypeError`), and non-JSON claims such as a `datetime` + or `NaN` raise `JwtError`. +- `is_fresh` counts the `Age` header `store_validators` now records (RFC 9111 + 4.2.3), a repeated `Cache-Control` directive keeps its first value, and + `is_not_modified` answers `False` for a non-numeric status. +- `json_query`: a surrogate pair written as two `\u` escapes finds its key; + `[01]`, `[-0]`, an index past 2**53-1, a bald `..`, an unpaired surrogate and a + raw control character in a quoted name raise `ValueError` (RFC 9535). +- JSON Schema `pattern` / `patternProperties` match as ECMA-262 does: `$` no + longer matches before a trailing newline, `\d` / `\w` / `\b` are ASCII, and + `\p{...}`, `\cX`, `\u{...}`, `[]` / `[^]` and named groups are understood. + `multipleOf` no longer raises `OverflowError` past float range. +- The VLM tab no longer freezes the window while the model answers, and the + Hotkeys, Scheduler and Triggers tabs show an engine started from Tools > Start + or a script as running. +- The USB Devices tab and the passthrough panel no longer run a ~5 s device + enumeration on the GUI thread, and one no longer stops the other's hotplug + watcher; email Poll now and Admin broadcast no longer freeze the window. +- Every MCP `tools/call` is answered with `isError: true` whatever the + tool raises; a plain HTTP POST answers in its body even on a concurrent + server; an unwritable audit log no longer fails a tool that ran. +- Auto-click refuses an interval below 1 ms; the Assertions, Data Source, + LLM Planner, Self-Healing, Media Checks, Trace Replay and Variables tabs + show framework and decode errors instead of leaking them; a recording that + did not start is reported; template crops write into non-ASCII folders; + the Live HUD no longer logs its own sampling; the View > Tabs menu no + longer leaks on every rebuild. +- `VariableScope.update_many` sets nothing when any name is invalid. +- Sub-pixel and scale matching accept 16-bit and float images and contain + OpenCV errors; one unscorable scale no longer aborts a sweep. +- An aborted `drag_path` releases where the pointer stopped; a `click` step + without a point clicks in place instead of at (0, 0). +- `AC_rate_limit` takes a changed rate or capacity for a reused name. +- `wait_until_app_idle` never sleeps past its timeout or spins. +- Reading an in-memory store while another thread writes it no longer + raises; hand-written skill tags given as a string stay one tag. +- `ac_run_saga` reports `compensation_errors`. +- `fuse_elements`, `observation_index` and `classify_effect` read elements + with `bounds` or without a size; unchanged zero-area elements are no + longer reported as changes. +- `image_quality` and `motion_regions` measure 16-bit and float frames in + 8 bits and contain OpenCV errors. +- `client_point` / `get_client_rect` return `None` for a minimized window; + `wait_for_focus_change` refuses a NaN timeout. +- Every `AC_*` flag parameter reads a string by its spelling: `"false"`, + `"no"`, `"off"` and `"0"` are off. 85 adapters and two flow commands + took any non-empty string as on. +- `set_field_text` clears with Backspace on macOS; `deep_merge` no longer + shares nested values with its inputs; co-failure clustering keeps a + one-name run whole; TOTP time errors are `TOTPError`. +- `AC_repair_*` commands and MCP tools without `db` see each other's + suggestions. +- Quarantine stores sharing a file no longer overwrite each other's names. +- ACME renewals never overlap after a restart; force mode accepts NumPy + bboxes; a plugin that fails to import is reported, not raised. +- Test selection, sharding and flakiness read finished runs, so runs + killed mid-flight no longer hide a flow's history. +- `find_lines` finds lines shorter than 50 px and reads 16-bit images; + `annotate_screenshot` keeps 16-bit and float tones; profiles survive huge + values; `True` and `1` are distinct values. +- On a Retina Mac, `[left, top, right, bottom]` region captures (colour, + HSV, histogram, SSIM, contrast, colour waits, QR, VLM) are taken in points, + so region results line up with the mouse instead of doubling. +- Script Builder `region` hints name the convention each command reads: + `[x, y, width, height]` for the matchers, OCR and most vision commands, + `[left, top, right, bottom]` for the colour, histogram, SSIM and QR + commands. Most said left, top, right, bottom whatever the command read. +- On Windows, `[left, top, right, bottom]` regions on a monitor other than + the primary one were captured black by the colour, HSV, histogram, SSIM, + contrast, preprocess, colour-wait, QR, colour-stats, VLM and MCP screenshot + paths. They capture every monitor through the new + `cv2_utils.region_capture.grab_screen_region`. +- `AC_wait_actionable` samples stability on the monitor where the target was + matched, instead of reading a target off the primary monitor as stable. +- `match_color` / `match_color_all` return screen coordinates for a region + search, score only a template's coloured pixels, and cap their candidates. +- Mark labels stay beside marks at negative coordinates and inside bounds + when crowded. +- `snapshot_json` matches its own payload; `match_json` takes one ignored + path as a string. +- Action lists nested in `AC_circuit_call`, `AC_with_modifiers`, + `AC_bulkhead_run` and similar commands run on the executor running the + outer list, so `run_on_devices` keeps each device's variables. +- Dark-theme widgets classify correctly in `classify_icon`; `ß` collates as + `ss`; checksums accept whole floats and numbers past 4,300 digits; + `wait_for_composition_commit` never sleeps past its timeout; a unique + `ccorr_normed` match is no longer reported ambiguous; OCR text satisfies + `text_present`; `flow_order` reads `bbox`, `bounds` and match objects. +- Set-of-marks numbers OCR boxes, and grounding consensus votes by each + element's real geometry; consensus refuses non-finite points. +- Borderless tables are found left of x = 0 (monitors left of the primary). +- The recording editors leave `${var}` sleeps alone; the flow debugger takes + an `{"auto_control": [...]}` file; a compliance framework can be passed + as one string. +- Python 3.15 turns UTF-8 mode on by default. `AC_shell_to_var`, the MCP + `shell_command` tool, `ShellManager` and the remote host's `status` still + decode a Windows console program's output in its code page there, + instead of as UTF-8: `sc query` raised, and the others returned + replacement characters. The SBOM skips a distribution that has no + metadata instead of failing on 3.15, or listing it as `unknown` before. +- WebRTC host annotations are drawn at the viewer's position on the + captured screen, including other monitors and scaled displays. +- Remote desktop hosts map viewer input and the broadcast cursor through + the captured frame's origin, so clicks land correctly on a second monitor, + a capture region, or a virtual desktop that extends above or left of the + primary screen. `dispatch_input` takes an optional `origin`. +- Perceptual diff contains OpenCV errors and checks its budget unrounded. +- `find_text_regions` / `find_text_lines` return screen coordinates for a + region search; `match_rotated_all` returns every match, not one per pose. +- Actionability treats a falsy enabled probe as disabled, accepts NumPy + stability tokens, and can pass with no stability wait. +- The generated project's keyword example validates and runs. +- Grid fill counts a spanning box once, at its anchor cell, and reads + Tesseract's box shape. +- Semantic replay keeps the recorded point for an unnamed or off-screen + anchor, and recording anchors on the clicked control, not its window. +- A failed self-healing lookup is a failed step instead of aborting replay. +- Video motion checks clip their region and stream frames; preprocessing + handles 16-bit, single-channel and float images; OpenCV errors in both are + recorded as step failures. +- HTML report generation is linear in the number of records. +- D-Bus socket addresses are unescaped, and unmarshallable values raise + `DBusError`. - The Flow Editor opens action files saved with a BOM, keeps a wrapped file's other keys on save, and writes atomically. - The region selector (template cropping, OCR / screenshot / WebRTC regions) diff --git a/CLAUDE.md b/CLAUDE.md index 7e8826f65..4b155da56 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -163,7 +163,7 @@ These tools own the generic rules (bare `except`, mutable defaults, unused names Suppressions need an inline justification — `# noqa: # reason: ` or `# nosec B404 # reason: `. Blanket file- or module-level suppressions are forbidden. -A broad `except` (`Exception`, `BaseException`, bare) that swallows rather than re-raises needs `# reason:` on its own `except` line. `test/unit_test/headless/test_broad_except_reasons.py` fails CI on one that does not — the linters cannot: CI runs `ruff` with its default rules (no `BLE`) and does not run pylint. +A broad `except` (`Exception`, `BaseException`, bare) that swallows rather than re-raises needs `# reason:` on its own `except` line. `test/unit_test/headless/test_broad_except_reasons.py` fails CI on one that does not — the linters cannot: CI runs `ruff` with the rule set pinned in `pyproject.toml` (`E4`/`E7`/`E9`/`F` plus `E501`; no `BLE`) and does not run pylint. ## Stage commits, `Progress.md`, `docs/updates/` and `architecture.md` @@ -213,3 +213,4 @@ Workspace rule shared by every repository under `D:\Codes` (full text: `D:\Codes - JSON action command names use the `AC_` prefix (e.g. `AC_click_mouse`); MCP tools use `ac_`. - Platform backends are named `{platform}_{function}.py` (e.g. `win32_ctype_mouse_control.py`). - Virtual key mappings live in `core/utils/*_vk.py` per platform. +- Two PyPI packages, both published by CI: a push to `main` releases `je_auto_control` (the `publish` job of `stable.yml`), and a push to `dev` that passes the headless suite and changes what the package ships releases `je_auto_control_dev` (the `publish-dev` job of `dev.yml`, `scripts/dev_release.py`). Never bump a version by hand; the version in `dev.toml` is only a floor. `dev.toml` must declare what `pyproject.toml` declares, so a change to dependencies, extras, scripts, entry points or `[tool.setuptools]` goes into both files (`test/unit_test/headless/test_dev_toml_parity.py` fails otherwise). diff --git a/Progress.md b/Progress.md index 9aebbe833..be164bd33 100644 --- a/Progress.md +++ b/Progress.md @@ -176,6 +176,9 @@ pip install --dry-run --only-binary=:all: --platform win_arm64 --python-version - **部分超出螢幕的 `screen_region` 被補黑**:`monitor_layout/logical_frame.py:143` 沒有先和畫面取交集,PIL `crop` 補零,可能回傳螢幕外的命中;寬或高為負時丟裸 `ValueError`。做法:先取交集(回傳裁過的原點),非正的寬高丟框架例外。 - **OCR 跨框比對漏掉從長框中段開始的字串**:`ocr/text_span.py:330` 的視窗超過「目標長度+40」就整個丟掉最左框,即使目標從那框開始;`"Save As"` 在長句框之後就找不到。做法:只有剩下的部分仍不短於目標時才丟左框。 - **負座標的中心點差一**:`wrapper/auto_control_image.py:48`、`:73` 的 `int((x1 + x2) / 2)` 向零截斷。做法:`(x1 + x2) // 2`。 +- **Unicode 打字把換行與 Tab 當字元送**:`utils/text_unicode/text_unicode.py:49` `plan_unicode_keys("a\nb\tc")` 送出碼位 10 與 9,多數程式會丟掉 Unicode 的 LF;`write` 早就把它們對到 Return/Tab(`WRITE_CONTROL_KEYS`)。這個模組被 `wrapper/auto_control_keyboard.py:27` 載入。做法:控制空白改成按鍵。 +- **鍵盤配置表的 Shift 半邊與非美式鍵**:`utils/keyboard_layout/keyboard_layout.py:98-100` 的 Shift 半邊是死鍵時退回未按 Shift 的字(美式國際配置的 Shift+6 回 `'6'`,契約是回 `None`);`:96` 只翻譯美式鍵碼,德/法/北歐鍵盤的 `VK_OEM_102`(0xE2)與英式 `VK_OEM_8` 永遠沒有標籤;`:68-74` 把原型設在全程序共用的 `ctypes.windll.user32` 上,之後別的呼叫者用 `c_ubyte` 陣列呼叫 `ToUnicodeEx` 會 `ArgumentError`。Jeffrey_RPA 的 `_gui_control.py:3322` 呼叫 `ac.foreground_keyboard_layout()`。做法:死鍵半邊回 `None`;候選鍵碼加上 0xDF、0xE1、0xE2…;改用私有的 `ctypes.WinDLL("user32")`。 +- **剪貼簿格式名稱 `None`**:`utils/clipboard_formats/clipboard_formats.py:46` `_coerce` 把 tuple/list 描述的 `None` 名稱變成字串 `"None"`,dict 形式卻是 `""`,`diff_formats` 因此回報有變動。Jeffrey_RPA 的 `_gui_control.py:1126` 呼叫 `ac.clipboard_formats()`。做法:兩種形式都把 `None` 正規化成 `""`。 **解除條件**:Jeffrey_RPA 沒有批次在跑(`webrunner.pid` 的行程不在、Discord bot 停止);改完在 Jeffrey_RPA 跑 `test/test_je_facade.py`。 @@ -429,6 +432,62 @@ MCP 工具的檔案參數(`path`、`file_path`、`db`、`image_path`、`golden **為什麼要拍板**:根目錄從哪來(新的環境變數、沿用 `roots/list`、或兩者),唯讀模式要不要預設開啟; 預設開啟會讓現有讀取工作區外檔案的用法失效。 +同一個問題也在 `ac_resolve_ref`/`ac_resolve_refs`(`_factories.py:7182`,標為 `READ_ONLY`):`file://` 沒有 +`base_dir` 限制,`env://` 可讀任何環境變數,包括放 API 金鑰的那些,結果直接回給模型。`secret://` 已經拒絕; +`env://` 要不要改成允許清單、`file://` 要不要套同一個根目錄,跟上面一起決定。 + +--- + +## Windows 的 DPI 感知是系統層級,混合 DPI 的螢幕座標被虛擬化 + +`DECIDE` — 改成 per-monitor 會移動那些螢幕上的所有座標,Jeffrey_RPA 在那些螢幕上錄的座標與樣板要重錄 + +`windows/screen/win32_screen.py:50` 在 import 時呼叫 `SetProcessDPIAware()`,那是系統 DPI 感知,不是 +per-monitor。DPI 與主螢幕不同的螢幕會被 Windows 虛擬化:本機第二螢幕 125%,實際 1920×1080,但 Win32、 +`mss` 與 Qt 都回報 `(1920, -164, 1536, 864)`,截圖是 Windows 縮小過的影像,那個螢幕上的樣板比對與 OCR +用的是模糊的畫面。同檔註解說之後「所有 Win32 座標查詢都會拿到實體像素」,只在主螢幕 DPI 的螢幕上成立。 + +**做法**:先呼叫 `SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)`,失敗再退回 +`SetProcessDPIAware()`,並改正註解;`utils/monitor_layout` 的換算與 `gui/_screen_geometry.py` 一起檢查。 + +**為什麼要拍板**:這個檔在 Jeffrey_RPA 正在跑的截圖路徑上。換成 per-monitor 之後,縮放螢幕上的座標與截圖 +尺寸都會變,既有的樣板和錄好的座標在那些螢幕上會失準。 + +--- + +## `pil_screenshot`/`screenshot` 的區域擷取在 Windows 只看得到主螢幕 + +`BLOCKED` — 要改的是 Jeffrey_RPA 正在跑的截圖路徑(`cv2_utils/screenshot.py`、`wrapper/auto_control_screen.py`、`utils/window_capture/window_capture.py`),依工作區規則在它執行期間不動 + +`cv2_utils/screenshot.py:58` 把 `screen_region` 交給 `ImageGrab.grab(bbox=...)`,Pillow 在 Windows 沒帶 +`all_screens=True` 時只擷取主螢幕再裁切,主螢幕外的部分補黑。分析類指令已改走 +`cv2_utils/region_capture.grab_screen_region`(顏色、直方圖、SSIM、對比、顏色等待、QR、VLM、MCP 截圖), +下面這些仍是舊路徑,在主螢幕左側或上方的螢幕得到全黑影像: + +- `pil_screenshot(screen_region=...)`、`screenshot(screen_region=...)` 與 `AC_screenshot`。 +- `utils/pytest_plugin/keywords.py:41` `keyword_screenshot`(與 `AC_screenshot` 同一語意,一起改)。 +- `utils/window_capture/window_capture.py:66` `capture_window`:視窗在副螢幕時截到黑的。 +- `utils/set_of_marks/set_of_marks.py:121` 把標記畫在 `pil_screenshot()`(只有主螢幕)上,副螢幕的元件沒有標記。 + +**做法**:`pil_screenshot` 的區域路徑在 Windows 改走 `grab_screen_region`(它已處理 DPI 與負座標), +`capture_window` 同樣;set-of-marks 改用 `grab_logical(None)` 並把原點加回標記座標。 + +--- + +## macOS 的 `grab_logical` 在 Retina 上是像素座標,而且只看得到主螢幕 + +`BLOCKED` — `utils/monitor_layout/logical_frame.py` 在 Jeffrey_RPA 正在跑的截圖路徑上,依工作區規則在它執行期間不動 + +`grab_logical` 在 macOS 呼叫 `ImageGrab.grab(all_screens=True)`。讀 Pillow 12.3.0 的 darwin 分支:`all_screens` +不被使用,`screencapture -x` 只擷取主螢幕;Retina 螢幕的影像是點座標的 2 倍(Pillow 文件:「screen captures will +be at 2x if on a Retina screen」,`scale_down=True` 只在帶 `bbox` 時生效)。`logical_virtual_rect` 只讀 Windows 的 +`GetSystemMetrics`,所以 macOS 不縮放:樣板比對、OCR 與其他走 `grab_logical` 的定位,在 Retina 上回傳的座標是 +滑鼠(Quartz,點座標)的 2 倍,副螢幕上的目標則找不到。GitHub 的 macOS runner 是 1x 虛擬螢幕,CI 測不到。 + +**做法**:darwin 上以 `CGDisplayBounds`/`CGGetActiveDisplayList` 取得各螢幕的點座標範圍;有 `region` 時交給 +`ImageGrab.grab(bbox=..., scale_down=True)`(`screencapture -R` 接受全域點座標,包括負值),整個桌面則逐螢幕擷取、 +各自縮到點座標後拼接,原點取所有螢幕的最小 x/y。需要在 Retina Mac 上實測。 + --- ## 遠端桌面的 viewer 槽位由各面板共用 @@ -444,3 +503,19 @@ MCP 工具的檔案參數(`path`、`file_path`、`db`、`image_path`、`golden 被別人取代時通知原本的面板收掉自己的視窗。或是反過來讓每個面板持有自己的 viewer,不經 registry。 **為什麼要拍板**:`AC_remote_*` 指令與 MCP 工具依賴「registry 裡就是那一個 viewer」,改成多槽位要一起改它們的語意。 + +--- + +## `test_usb_acl_prompt.py` 讓 Python 3.10 的 headless 測試間歇 segfault + +`TODO` — `test/unit_test/headless/test_usb_acl_prompt.py::test_bridge_remember_persists_acl_rule` 在 `coverage run -m pytest` 下讓行程 SIGSEGV(exit 139),整個 `pytest-headless` job 因此失敗:2026-09-26 連續三次 AutoControl Code Quality(ubuntu-22.04/3.10),2026-09-30 一次(macos-14/3.10);同一次其他版本都過,之後的 run 又過,所以是間歇的。原因還沒查:先在 3.10 開 `faulthandler` 重跑這一支,看崩在哪個原生呼叫。 + +--- + +## 套件閘門的預設改成拒絕 + +`BLOCKED` — 等含警告的版本出去之後再發兩版 + +`AC_add_package_to_executor`/`AC_add_package_to_callback_executor` 前面已有套件閘門(工作區 X-12),但沒設定時仍會載入任何套件、只發 `DeprecationWarning`。兩個版本之後,在 `utils/package_manager/package_manager_class.py` 的 `PackageManager.__init__` 把 `allow_arbitrary_packages` 改成 `False`,拿掉 `_check_allowed` 裡的警告分支,並更新三份 README 的「Package gate」段落、`docs/source/{Eng,Zh}/doc/keyword_and_executor/keyword_and_executor_doc.rst` 與 `docs/source/API/utils/package_manager.rst`,`CHANGELOG.md` 記成破壞性變更。 + +**先決定**:只跑動作檔、沒有 Python 宿主程式的使用者(`je_auto_control` CLI、socket/REST/MCP server、排程器)要怎麼放行套件。 diff --git a/README.md b/README.md index fa7e40b3f..69442eea5 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ from JSON files / CLI / servers, and a **GUI tab**. Nothing is GUI-only. - **One API, seven platforms.** `wrapper/platform_wrapper.py` picks the backend at import time; your script does not change between Windows, macOS, X11, and Wayland. -- **Scriptable without Python.** 775 `AC_*` commands cover the whole feature set, so a +- **Scriptable without Python.** 776 `AC_*` commands cover the whole feature set, so a JSON file can do anything the library can — including loops, branches, try/catch, macros, and variables. - **Headless by default.** `import je_auto_control` never loads Qt. The GUI is an @@ -143,7 +143,7 @@ desktop app; tab commands live in the window's **Actions** menu. | Capability | Python API | `AC_*` command | GUI tab | |---|---|---|---| | Mouse | `click_mouse`, `set_mouse_position`, `mouse_scroll` | `AC_click_mouse` | Auto Click | -| Keyboard | `write`, `hotkey`, `type_keyboard` | `AC_write`, `AC_hotkey` | Auto Click | +| Keyboard | `write`, `write_secret`, `hotkey`, `type_keyboard` | `AC_write`, `AC_write_secret`, `AC_hotkey` | Auto Click | | Screen & pixels | `screenshot`, `screen_size`, `get_pixel` | `AC_screenshot` | Screenshot | | Image matching | `locate_image_center`, `locate_and_click` | `AC_locate_and_click` | Image Detect | | OCR text | `click_text`, `wait_for_text`, `read_text_in_region` | `AC_click_text`, `AC_wait_text` | OCR Reader | @@ -154,7 +154,7 @@ desktop app; tab commands live in the window's **Actions** menu. | Natural-language planner | `plan_actions`, `run_from_description` | `AC_llm_plan` | LLM Planner | | Computer-use agent | `AgentLoop`, `run_agent` | `AC_run_agent` | Computer Use | | Record & replay | `record`, `stop_record` | `AC_record`, `AC_stop_record` | Record | -| JSON scripting | `execute_action`, `execute_files` | all 775 commands | Script, Script Builder | +| JSON scripting | `execute_action`, `execute_files` | all 776 commands | Script, Script Builder | | Variables & flow control | `execute_action_with_vars` | `AC_set_var`, `AC_loop`, `AC_for_each`, `AC_try`, `AC_retry` | Variables | | Data-driven runs | — | `AC_for_each_row` (CSV / JSON / SQLite / Excel) | Data Sources | | Assertions | `assert_text`, `assert_image` | `AC_assert_text` + 20 more | Assertions | @@ -215,6 +215,8 @@ still goes on to the end), so a CI step fails with it. The legacy All servers bind to `127.0.0.1` unless you opt in explicitly. +**Package gate.** `AC_add_package_to_executor` and `AC_add_package_to_callback_executor` import a Python package and register its members as commands, so an action list arriving over any of these surfaces could load `os` or `subprocess`. The host program decides what may load: `executor.allow_packages("name", …)` lists the packages (submodules included) and `executor.set_allow_arbitrary_packages(False)` refuses the rest before importing them. Neither is an `AC_*` command, so an action list cannot open its own gate. A refused package fails that action with `AutoControlExecuteActionException`. Until the host calls either switch, any package still loads but raises a `DeprecationWarning`: a future release will refuse unlisted packages by default. + ### How the remote-desktop wire protocol works Worth knowing before you expose a host, and described nowhere else in the diff --git a/README/README_zh-CN.md b/README/README_zh-CN.md index 69e548699..508c2bc5b 100644 --- a/README/README_zh-CN.md +++ b/README/README_zh-CN.md @@ -20,7 +20,7 @@ - **一套 API,七个平台。** `wrapper/platform_wrapper.py` 在导入时挑选后端;同一份脚本在 Windows、macOS、X11 与 Wayland 上都不需要改写。 -- **不写 Python 也能脚本化。** 775 个 `AC_*` 命令覆盖全部功能,因此一个 JSON 文件能做到库 +- **不写 Python 也能脚本化。** 776 个 `AC_*` 命令覆盖全部功能,因此一个 JSON 文件能做到库 能做的任何事——包含循环、分支、try/catch、宏与变量。 - **默认无头运行。** `import je_auto_control` 绝不会加载 Qt。GUI 是可选包,包在同一个无头内核之外。 - **四种定位方式。** 模板匹配、OCR、无障碍树、视觉语言模型——可通过锚点定位器与自愈回退串接组合。 @@ -131,7 +131,7 @@ python -c "import je_auto_control; je_auto_control.start_autocontrol_gui()" | 能力 | Python API | `AC_*` 命令 | GUI 标签页 | |---|---|---|---| | 鼠标 | `click_mouse`、`set_mouse_position`、`mouse_scroll` | `AC_click_mouse` | Auto Click | -| 键盘 | `write`、`hotkey`、`type_keyboard` | `AC_write`、`AC_hotkey` | Auto Click | +| 键盘 | `write`、`write_secret`、`hotkey`、`type_keyboard` | `AC_write`、`AC_write_secret`、`AC_hotkey` | Auto Click | | 屏幕与像素 | `screenshot`、`screen_size`、`get_pixel` | `AC_screenshot` | Screenshot | | 图像匹配 | `locate_image_center`、`locate_and_click` | `AC_locate_and_click` | Image Detect | | OCR 文字 | `click_text`、`wait_for_text`、`read_text_in_region` | `AC_click_text`、`AC_wait_text` | OCR Reader | @@ -142,7 +142,7 @@ python -c "import je_auto_control; je_auto_control.start_autocontrol_gui()" | 自然语言规划 | `plan_actions`、`run_from_description` | `AC_llm_plan` | LLM Planner | | Computer-use agent | `AgentLoop`、`run_agent` | `AC_run_agent` | Computer Use | | 录制与回放 | `record`、`stop_record` | `AC_record`、`AC_stop_record` | Record | -| JSON 脚本 | `execute_action`、`execute_files` | 全部 775 个命令 | Script、Script Builder | +| JSON 脚本 | `execute_action`、`execute_files` | 全部 776 个命令 | Script、Script Builder | | 变量与流程控制 | `execute_action_with_vars` | `AC_set_var`、`AC_loop`、`AC_for_each`、`AC_try`、`AC_retry` | Variables | | 数据驱动执行 | — | `AC_for_each_row`(CSV/JSON/SQLite/Excel) | Data Sources | | 断言 | `assert_text`、`assert_image` | `AC_assert_text` 等 21 个 | Assertions | @@ -200,6 +200,8 @@ je_auto_control version 除非明确指定,所有服务器都绑定在 `127.0.0.1`。 +**包闸门。** `AC_add_package_to_executor` 与 `AC_add_package_to_callback_executor` 会导入 Python 包并把成员注册成命令,所以从上面任何一个入口送来的动作列表都可能加载 `os` 或 `subprocess`。哪些包可以加载,由宿主程序决定:`executor.allow_packages("name", …)` 列出可以加载的包(含子模块),`executor.set_allow_arbitrary_packages(False)` 会在导入前拒绝其他包。这两个都不是 `AC_*` 命令,所以动作列表不能自己打开闸门。被拒绝的包会让该动作以 `AutoControlExecuteActionException` 失败。宿主程序调用任一个开关之前,任何包仍会加载,但会发出 `DeprecationWarning`:之后的版本会默认拒绝清单以外的包。 + ### 远程桌面的线路协议 把主机开放出去之前值得先了解,而且这一段在其他文档里都没有写。默认传输是**裸 diff --git a/README/README_zh-TW.md b/README/README_zh-TW.md index 61af44442..404537ccd 100644 --- a/README/README_zh-TW.md +++ b/README/README_zh-TW.md @@ -20,7 +20,7 @@ - **一套 API,七個平台。** `wrapper/platform_wrapper.py` 在匯入時挑選後端;同一份腳本在 Windows、macOS、X11 與 Wayland 上都不需要改寫。 -- **不寫 Python 也能腳本化。** 775 個 `AC_*` 指令涵蓋全部功能,因此一個 JSON 檔能做到函式庫 +- **不寫 Python 也能腳本化。** 776 個 `AC_*` 指令涵蓋全部功能,因此一個 JSON 檔能做到函式庫 能做的任何事——包含迴圈、分支、try/catch、巨集與變數。 - **預設無頭執行。** `import je_auto_control` 絕不會載入 Qt。GUI 是選用套件,包在同一個無頭核心之外。 - **四種定位方式。** 樣板比對、OCR、無障礙樹、視覺語言模型——可透過錨點定位器與自癒後備串接組合。 @@ -131,7 +131,7 @@ python -c "import je_auto_control; je_auto_control.start_autocontrol_gui()" | 能力 | Python API | `AC_*` 指令 | GUI 分頁 | |---|---|---|---| | 滑鼠 | `click_mouse`、`set_mouse_position`、`mouse_scroll` | `AC_click_mouse` | Auto Click | -| 鍵盤 | `write`、`hotkey`、`type_keyboard` | `AC_write`、`AC_hotkey` | Auto Click | +| 鍵盤 | `write`、`write_secret`、`hotkey`、`type_keyboard` | `AC_write`、`AC_write_secret`、`AC_hotkey` | Auto Click | | 螢幕與像素 | `screenshot`、`screen_size`、`get_pixel` | `AC_screenshot` | Screenshot | | 影像比對 | `locate_image_center`、`locate_and_click` | `AC_locate_and_click` | Image Detect | | OCR 文字 | `click_text`、`wait_for_text`、`read_text_in_region` | `AC_click_text`、`AC_wait_text` | OCR Reader | @@ -142,7 +142,7 @@ python -c "import je_auto_control; je_auto_control.start_autocontrol_gui()" | 自然語言規劃 | `plan_actions`、`run_from_description` | `AC_llm_plan` | LLM Planner | | Computer-use agent | `AgentLoop`、`run_agent` | `AC_run_agent` | Computer Use | | 錄製與重播 | `record`、`stop_record` | `AC_record`、`AC_stop_record` | Record | -| JSON 腳本 | `execute_action`、`execute_files` | 全部 775 個指令 | Script、Script Builder | +| JSON 腳本 | `execute_action`、`execute_files` | 全部 776 個指令 | Script、Script Builder | | 變數與流程控制 | `execute_action_with_vars` | `AC_set_var`、`AC_loop`、`AC_for_each`、`AC_try`、`AC_retry` | Variables | | 資料驅動執行 | — | `AC_for_each_row`(CSV/JSON/SQLite/Excel) | Data Sources | | 斷言 | `assert_text`、`assert_image` | `AC_assert_text` 等 21 個 | Assertions | @@ -200,6 +200,8 @@ je_auto_control version 除非明確指定,所有伺服器都綁在 `127.0.0.1`。 +**套件閘門。** `AC_add_package_to_executor` 與 `AC_add_package_to_callback_executor` 會匯入 Python 套件並把成員註冊成命令,所以從上面任何一個入口送來的動作清單都可能載入 `os` 或 `subprocess`。哪些套件可以載入,由宿主程式決定:`executor.allow_packages("name", …)` 列出可以載入的套件(含子模組),`executor.set_allow_arbitrary_packages(False)` 會在匯入前拒絕其他套件。這兩個都不是 `AC_*` 命令,所以動作清單不能自己打開閘門。被拒絕的套件會讓該動作以 `AutoControlExecuteActionException` 失敗。宿主程式呼叫任一個開關之前,任何套件仍會載入,但會發出 `DeprecationWarning`:之後的版本會預設拒絕清單以外的套件。 + ### 遠端桌面的線路協定 把主機開出去之前值得先知道,而且這段在其他文件裡都沒有寫。預設傳輸是**裸 TCP diff --git a/architecture.md b/architecture.md index fb56f425a..92c255dd0 100644 --- a/architecture.md +++ b/architecture.md @@ -36,6 +36,7 @@ entry points → execution core (`utils/executor/`) → headless capabilities (` | `examples/`, `benchmarks/` | Runnable example scripts; latency smoke benchmark. | | `docker/`, `k8s/helm/`, `ci_templates/` | Container images and backend verification harnesses, Helm chart, GitLab CI template. | | `browser-extension/`, `AutoControl/`, `exe/`, `autocontrol_driver/` | Manifest v3 companion extension, project-template sample, packaged GUI launcher, driver build script. | +| `scripts/` | Release tooling, not shipped: `dev_release.py` numbers and gates the dev-channel package (§3). | ## 3. Entry points and public interfaces @@ -52,6 +53,7 @@ entry points → execution core (`utils/executor/`) → headless capabilities (` | LSP | `autocontrol-lsp` → `autocontrol_lsp.server.server:run`; `python -m autocontrol_lsp.server` | Command list is read from the live executor. | | GUI | `start_autocontrol_gui()` in `gui/__init__.py`; `exe/start_autocontrol_gui.py` | Needs `pip install je_auto_control[gui]`; PySide6 is imported only under `gui/`. | | Action lint | `python -m je_auto_control.utils.action_lint` | Used by `.github/workflows/action-json-lint.yml`. | +| PyPI packages | `je_auto_control` (stable), `je_auto_control_dev` (dev channel) | Both ship the same `je_auto_control` import package. Stable: a push to `main` runs the `publish` job of `stable.yml`, which bumps `pyproject.toml`, uploads and tags. Dev: the `publish-dev` job of `dev.yml` runs after the headless suite on a push to `dev`, builds from `dev.toml` and uploads when the commit is still the tip of `dev` and the wheel differs from the newest published one; `scripts/dev_release.py` takes the version from PyPI (newest release plus one patch), so nothing is committed back. `dev.toml` declares what `pyproject.toml` declares (`test_dev_toml_parity.py`). | ## 4. Main flows @@ -79,7 +81,8 @@ the CLI, the scheduler, triggers, hotkeys, webhooks, the MCP run tool, the GUI) TCP socket_server | REST rest_api | MCP mcp_server | utils/scheduler | utils/triggers | utils/chatops → execute_action → same Executor instance → flow A AC_web_run / AC_web_run_actions → utils/webrunner_bridge/bridge.py - → je_web_runner.utils.executor.action_executor.executor.event_dict["WR_*"] + → je_web_runner.utils.executor.action_executor.execute_one(["WR_*", params]) + (event_dict["WR_*"] on a WebRunner without execute_one); a failure → WebRunnerBridgeError ``` **C. Record → edit → generate code** @@ -129,18 +132,25 @@ wrapper/auto_control_record.record → OS listener (e.g. windows/record/win32_in | Jeffrey_RPA | Editable install of **this working tree**: uncommitted changes here reach it immediately. Single facade `JeffreyRPA/_gui_control.py`. | Top-level names (e.g. `click_mouse`, `hotkey`, `write`, `screen_size`, `get_pixel`, `post_click_to_window`) and internal paths `je_auto_control.wrapper.auto_control_window`, `je_auto_control.wrapper.auto_control_keyboard.WRITE_CONTROL_KEYS`, `je_auto_control.utils.monitor_layout` (`logical_virtual_rect`, `enumerate_monitors`), and `wrapper.platform_wrapper.keyboard_keys_table` / `mouse_keys_table` — it validates every key name a user types against the keyboard table and reverse-looks-up recorded virtual keys through it, so a name removed there becomes a rejected hotkey over in that repo. | | PyBreeze | Subprocess `python -m je_auto_control --execute_str ` / `--execute_file `; on Windows the JSON string arrives double-encoded. | Legacy CLI flags; also embeds `je_auto_control.gui.main_widget.AutoControlGUIWidget` and calls `record` / `stop_record` in-process. | | TestPioneer | Optional extra `gui = ["je_auto_control"]`; `parallel_run` starts `python -m je_auto_control --execute_file `. | `execute_action`, `execute_files`, `RecordingThread`; the `--execute_file` flag. | +| WebRunner | Optional extra `autocontrol = ["je_auto_control>=0.0.224"]`; `je_web_runner/utils/autocontrol_bridge/` (`WR_ac_*`) imports this package only when one of those commands runs. | `je_auto_control.utils.executor.action_executor.executor`: `execute_action(actions, raise_on_error=True)`, whose record values it reads in action order, and `known_commands()`; its native commands send `AC_write` (`write_string`), `AC_write_secret` (`secret`, from `WR_ac_basic_auth`), `AC_type_keyboard` (`keycode`), `AC_get_keyboard_keys_table` (the `enter` or `return` key), `AC_locate_image_center` (`image`, `detect_threshold`) and `AC_click_mouse` (`mouse_keycode`, `x`, `y` in this DPI-aware process's coordinates). It refuses `AC_shell_command`, `AC_execute_process`, `AC_add_package_*`, `AC_execute_action`, `AC_execute_files`, `AC_run_agent` and `AC_web_*` by name, so renaming one of them would let it through WebRunner's bridge. | **Guarded by** `test/unit_test/headless/test_cross_project_contracts.py`: every legacy CLI flag (short and long, run as a -real child process, including PyBreeze's double-encoded `--execute_str`), the facade names in the three rows above +real child process, including PyBreeze's double-encoded `--execute_str`), the facade names in the rows above (Jeffrey_RPA's list is every `ac.` in `_gui_control.py`), the `auto_control_window` functions Jeffrey_RPA calls, -its three internal imports, the two key tables (shape everywhere, Windows key names on Windows), and -`AutoControlGUIWidget`. The test only knows what this table knows: when a consumer starts relying on something +its three internal imports, the two key tables (shape everywhere, Windows key names on Windows), +`AutoControlGUIWidget`, and the executor API and refused command names WebRunner's bridge relies on. The test only knows what this table knows: when a consumer starts relying on something new, add it to both. -**Outbound (optional):** `utils/webrunner_bridge/bridge.py` imports WebRunner's *internal* -`je_web_runner.utils.executor.action_executor.executor` lazily, for `AC_web_*` commands and `gui/webrunner_tab.py`. -`je_web_runner` is not a declared dependency; when it is missing the bridge raises `WebRunnerBridgeError`. -Moving that WebRunner module breaks the bridge. +**Outbound (optional):** `utils/webrunner_bridge/bridge.py` imports WebRunner's supported module +`je_web_runner.utils.executor.action_executor` lazily, for `AC_web_*` commands and `gui/webrunner_tab.py`: `executor` +(its `event_dict` lists and checks the `WR_*` names) and `execute_one`, which runs each command through WebRunner's +gates, retries and failure screenshots; a WebRunner without `execute_one` gets the `event_dict` callable directly. +The helpers send `WR_get_webdriver_manager`, `WR_to_url`, `WR_quit`, `WR_save_screenshot`, `WR_get_current_url`; +WebRunner's `test_public_api.py` guards those names and `execute_one`, and `test_webrunner_contract.py` here checks +them against the installed package. `je_web_runner` is not a declared dependency: `is_webrunner_available` looks it +up with `importlib.util.find_spec`, because importing it writes `WEBRunner.log` into the cwd, and a missing package +raises `WebRunnerBridgeError`. Any error from a `WR_*` command is re-raised as `WebRunnerBridgeError` (an +`AutoControlException`), so the executor records it instead of aborting the script. **Wire contract between AutoControl versions:** the Admin Console and DAG remote nodes drive other hosts through REST `POST /execute` (`{"actions": [...], "raise_on_error": bool}`), and those hosts may run an older release. @@ -180,6 +190,10 @@ first. `AC_*` command names and the legacy CLI flags are public too (action file - Flat exception hierarchy: every framework error derives from `AutoControlException`; assertion failures keep propagating. → CLAUDE.md › Coding Standards › Project-specific rules - Validate at boundaries and reject unknown command names; servers bind `127.0.0.1` unless explicitly opted in. → same +- `AC_add_package_to_executor` / `AC_add_package_to_callback_executor` pass the package gate in + `utils/package_manager/package_manager_class.py` before importing: `executor.allow_packages(...)` and + `executor.set_allow_arbitrary_packages(...)` are Python-only switches, never `AC_*` commands, so an action list + cannot open its own gate. Unconfigured, any package loads with a `DeprecationWarning` (workspace X-12). - No `print()` or runtime `assert` in library code; lazy imports for optional and platform deps; release platform resources in `finally` / `with`; guard shared state with locks or queues; pin dependency versions. → same - Size limits (cyclomatic ≤ 10, cognitive ≤ 15, function ≤ 75 lines, file ≤ 750 lines, line ≤ 120) are a review diff --git a/architecture_explore.md b/architecture_explore.md index 63987baf6..5d98e0e70 100644 --- a/architecture_explore.md +++ b/architecture_explore.md @@ -6,7 +6,7 @@ > 擷取每個模組的 docstring 與頂層公開名稱;統計數字取自實際檔案,非估算。 > 指令數與公開 API 數以 `executor.known_commands()` 與 `je_auto_control.__all__` 在工作樹上實測取得。 > -> **掃描時間**:2026-09-25 **版本**:`pyproject.toml` version `0.0.221` **分支**:`feat/coverage-to-80` +> **掃描時間**:2026-10-01 **版本**:`pyproject.toml` version `0.0.221` **分支**:`feat/coverage-to-80` --- @@ -19,10 +19,10 @@ iOS(WebDriverAgent)。核心能力是滑鼠/鍵盤控制、影像辨識、 | 指標 | 數值 | | --- | ---: | -| Python 模組總數(含周邊子專案) | 1,059 | -| 程式碼總行數 | 154,389 | +| Python 模組總數(含周邊子專案) | 1,063 | +| 程式碼總行數 | 157,101 | | `je_auto_control/utils/` 子套件數 | 310 | -| `AC_*` 動作指令數(`known_commands()` 實測) | 775 | +| `AC_*` 動作指令數(`known_commands()` 實測) | 776 | | 套件門面 `__all__` 公開名稱數 | 1,244 | | GUI 分頁數(`main_widget` 註冊) | 48 | | MCP 工具數(`build_default_tool_registry()` 實測) | 678 | @@ -49,7 +49,7 @@ USB/IP 協定、Prometheus 指標),以維持這條輕相依基線。 │ 全部只呼叫下面這一層,不含業務邏輯 ┌───────────────────────────────▼──────────────────────────────────────────┐ │ 執行核心 Execution Core │ -│ utils/executor/action_executor.py ── Executor.event_dict(775 個 AC_*) │ +│ utils/executor/action_executor.py ── Executor.event_dict(776 個 AC_*) │ │ utils/executor/flow_control.py ── 34 個區塊指令(迴圈/分支/try/巨集) │ │ utils/script_vars ── ${var} 插值 │ utils/json ── action 檔 I/O │ └───────────────────────────────┬──────────────────────────────────────────┘ @@ -154,7 +154,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | 模組 | 行數 | 職責 | | --- | ---: | --- | -| `je_auto_control/__init__.py` | 1,970 | **套件門面**。集中匯入並再匯出 1,200 個公開名稱,以功能區塊註解分段(callback/exception/executor/a11y/vision/clipboard…)。 | +| `je_auto_control/__init__.py` | 1,971 | **套件門面**。集中匯入並再匯出 1,200 個公開名稱,以功能區塊註解分段(callback/exception/executor/a11y/vision/clipboard…)。 | | `je_auto_control/__main__.py` | 87 | 舊版 argparse 進入點:`-e` 執行單檔、`-d` 執行整個目錄、`--execute_str` 執行 JSON 字串、`-c` 建立專案。 | | `je_auto_control/cli.py` | 353 | **主 CLI**(`je_auto_control` console script)。子命令:`run`(含 `--var`/`--dry-run`)、`validate`/`lint`、`list-commands`、`fmt`、`record`、`codegen`、`failure-bundle`、`list-jobs`、`start-server`、`start-rest`、`version`。所有子命令延遲匯入,確保不碰 Qt。 | | `je_auto_control/api/__init__.py` | 22 | 版本化整合進入點。 | @@ -178,7 +178,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `wrapper/_platform_linux.py` | 275 | X11 後端組裝(python-Xlib + 選用 uinput)。 | | `wrapper/_platform_wayland.py` | 58 | Wayland 後端組裝(libei/ydotool/grim)。 | | `wrapper/auto_control_mouse.py` | 491 | 滑鼠 API:位置讀寫、按下/放開/點擊、捲動、座標前處理、送訊息給指定視窗。 | -| `wrapper/auto_control_keyboard.py` | 368 | 鍵盤 API:鍵表查詢、按下/放開/敲擊、`write` 字串、`hotkey` 組合鍵、按鍵狀態偵測。**`type_keyboard` 與 `hotkey` 的放開走 `finally`**(見下)。 | +| `wrapper/auto_control_keyboard.py` | 404 | 鍵盤 API:鍵表查詢、按下/放開/敲擊、`write` 字串、`hotkey` 組合鍵、按鍵狀態偵測。**`type_keyboard` 與 `hotkey` 的放開走 `finally`**(見下)。 | | `wrapper/auto_control_screen.py` | 111 | 螢幕 API:`screen_size`、`screenshot`(可指定區域)、`get_pixel`。 | | `wrapper/auto_control_image.py` | 83 | 影像 API:`locate_all_image`、`locate_image_center`、`locate_and_click`。 | | `wrapper/auto_control_record.py` | 124 | 錄製 API:`record`/`stop_record`/`record_to_json`(支援 stop event 與逾時)。 | @@ -272,7 +272,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 ### 5.4.1 執行引擎與腳本資產 -> 24 個套件、約 14,553 行。 +> 24 個套件、約 14,689 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | @@ -283,19 +283,19 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `utils/dag/` | 536 | 跨主機 DAG 編排器(圖模型 + runner) | | `utils/decision_table/` | 112 | DMN 風格決策表:規則 + 命中策略,把分支外部化 | | `utils/deterministic/` | 116 | 決定性執行控制:固定亂數種子 + 凍結時鐘 | -| `utils/executor/` | 9,504 | **核心**。`Executor` 指令分派表(775 個 `AC_*`)、參數插值、乾跑、逐步 callback;`flow_control` 提供 34 個區塊指令(迴圈/分支/try/巨集/變數) | -| `utils/flow_debugger/` | 155 | action list 的單步除錯器與追蹤器 | -| `utils/input_macro/` | 451 | 定時輸入事件:錄製結果的整形(`timeline`/`InputRecorder`,Windows 與 macOS 共用)、重播與宣告式輸入序列 DSL | +| `utils/executor/` | 9,560 | **核心**。`Executor` 指令分派表(776 個 `AC_*`)、參數插值、乾跑、逐步 callback;`flow_control` 提供 34 個區塊指令(迴圈/分支/try/巨集/變數) | +| `utils/flow_debugger/` | 166 | action list 的單步除錯器與追蹤器 | +| `utils/input_macro/` | 462 | 定時輸入事件:錄製結果的整形(`timeline`/`InputRecorder`,Windows 與 macOS 共用)、重播與宣告式輸入序列 DSL | | `utils/json/` | 99 | action JSON 檔讀寫與正規化格式化(`fmt --check` 的後端) | -| `utils/json_store/` | 271 | JSON 字典檔持久化的共用小工具(內部管線) | +| `utils/json_store/` | 278 | JSON 字典檔持久化的共用小工具(內部管線) | | `utils/loop_guard/` | 158 | 機械式卡死迴圈偵測(agent loop 用) | | `utils/plugin_loader/` | 147 | 掃描外部 Python 外掛目錄並註冊其 `AC_` callable | | `utils/plugin_sdk/` | 80 | 外掛 SDK:透過 entry points 發佈/載入第三方 `AC_*` 指令 | -| `utils/project/` | 187 | 專案腳手架:建立目錄結構與範本 action 檔 | -| `utils/recording_edit/` | 150 | 不重錄的前提下裁切/過濾/縮放已錄製的 action list | -| `utils/saga/` | 100 | Saga 協調器:失敗時以 LIFO 補償動作回滾 | -| `utils/script_vars/` | 211 | 執行期變數作用域與 `${var}` / `${secrets.*}` 插值 | -| `utils/skill_library/` | 115 | 具名可重用 action 序列(skill)的持久化倉庫 | +| `utils/project/` | 183 | 專案腳手架:建立目錄結構與範本 action 檔 | +| `utils/recording_edit/` | 165 | 不重錄的前提下裁切/過濾/縮放已錄製的 action list | +| `utils/saga/` | 103 | Saga 協調器:失敗時以 LIFO 補償動作回滾 | +| `utils/script_vars/` | 218 | 執行期變數作用域與 `${var}` / `${secrets.*}` 插值 | +| `utils/skill_library/` | 145 | 具名可重用 action 序列(skill)的持久化倉庫 | | `utils/state_machine/` | 268 | 宣告式有限狀態機驅動 action JSON | | `utils/stubs/` | 311 | 為 `AC_*` 指令面產生型別 stub | | `utils/test_record/` | 70 | 全域測試紀錄單例,記錄每個動作的參數與例外 | @@ -303,7 +303,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 ### 5.4.2 框架基礎設施 -> 14 個套件、約 3,014 行。 +> 14 個套件、約 3,105 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | @@ -311,58 +311,58 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `utils/config_bundle/` | 424 | 使用者設定的單檔匯出/匯入 | | `utils/critical_exit/` | 132 | 監看緊急停止鍵的守護執行緒,用於中止失控腳本 | | `utils/diagnostics/` | 330 | 跨子系統的「一切正常嗎」健檢,附 `python -m` 進入點 | -| `utils/dbus_client/` | 703 | 只用標準函式庫的 D-Bus session bus 客戶端。原本在 `linux_wayland/` 為 portal 交握而寫,AT-SPI 無障礙後端成為第二個使用者後搬到這裡(`utils/` 在分層上在各 OS 套件之上) | +| `utils/dbus_client/` | 714 | 只用標準函式庫的 D-Bus session bus 客戶端。原本在 `linux_wayland/` 為 portal 交握而寫,AT-SPI 無障礙後端成為第二個使用者後搬到這裡(`utils/` 在分層上在各 OS 套件之上) | | `utils/exception/` | 213 | **例外階層根**。所有錯誤繼承 `AutoControlException`,加上集中式錯誤訊息字串(`exception_tags`) | -| `utils/failure_bundle/` | 219 | 可攜、已遮蔽的失敗診斷 ZIP(截圖 + 診斷 + log 尾段) | +| `utils/failure_bundle/` | 229 | 可攜、已遮蔽的失敗診斷 ZIP(截圖 + 診斷 + log 尾段) | | `utils/file_process/` | 40 | 目錄檔案列舉(`execute_dir` 的後端) | | `utils/logging/` | 168 | `autocontrol_logger` 單例 + 家目錄共用記錄檔 handler(`JE_AUTOCONTROL_LOG_FILE` 可改) | -| `utils/package_manager/` | 101 | 動態載入套件並把 executor 注入其中 | +| `utils/package_manager/` | 155 | 動態載入套件並把 executor 注入其中;載入前先過套件閘門(允許清單加上只能從 Python 呼叫的開關,工作區 X-12) | | `utils/path_guard/` | 114 | 命令列傳入路徑的正規化與邊界檢查(防路徑穿越) | | `utils/platform_id/` | 62 | 作業系統家族的單一判定點。`sys.platform` 原本在一百多處跟字面清單比對,而那些清單都沒有 BSD;`is_x11_unix()` 問的是「這是不是 X11 unix」,這才是守衛一直想問的問題 | -| `utils/shell_process/` | 263 | `ShellManager`:以 argv list 執行外部命令(禁用 `shell=True`) | +| `utils/shell_process/` | 279 | `ShellManager`:以 argv list 執行外部命令(禁用 `shell=True`) | | `utils/start_exe/` | 36 | 啟動另一個執行檔行程 | ### 5.4.3 排程、觸發與背景監看 -> 11 個套件、約 4,210 行。 +> 11 個套件、約 4,300 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | -| `utils/hotkey/` | 846 | 全域熱鍵守護行程,把 OS 層熱鍵綁到 action 檔(Win/macOS/X11 三後端) | +| `utils/hotkey/` | 852 | 全域熱鍵守護行程,把 OS 層熱鍵綁到 action 檔(Win/macOS/X11 三後端) | | `utils/idle_keepawake/` | 245 | 偵測使用者閒置時間並在無人值守執行期間阻止系統睡眠 | | `utils/lock_session/` | 166 | 鎖定工作站、等待解鎖並分類鎖定狀態轉換 | | `utils/observer/` | 234 | 反應式畫面觀察者,在出現/消失/變化時觸發 | -| `utils/recurrence/` | 398 | RFC 5545 重複規則解析與發生時間展開 | -| `utils/scheduler/` | 500 | 間隔式與 cron 式的 action JSON 排程器 | +| `utils/recurrence/` | 470 | RFC 5545 重複規則解析與發生時間展開 | +| `utils/scheduler/` | 506 | 間隔式與 cron 式的 action JSON 排程器 | | `utils/session_guard/` | 62 | 驅動輸入前先偵測工作階段是否已鎖定/非互動 | -| `utils/triggers/` | 1,377 | 事件驅動觸發引擎:影像/視窗/像素/檔案/webhook/IMAP 郵件 | +| `utils/triggers/` | 1,383 | 事件驅動觸發引擎:影像/視窗/像素/檔案/webhook/IMAP 郵件 | | `utils/voice/` | 97 | 語音指令路由:把辨識到的語句對應到 `AC_*` action list | | `utils/watchdog/` | 195 | 背景彈窗/中斷看門狗,供無人值守自動化 | | `utils/watcher/` | 90 | 無頭輪詢原語:滑鼠位置、像素顏色、log tail | ### 5.4.4 輸入模擬與動作品質 -> 22 個套件、約 2,768 行。 +> 22 個套件、約 2,843 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | | `utils/act_in_view/` | 86 | 先把目標捲進視野,待其可操作後再動作 | -| `utils/act_modes/` | 68 | actionability 閘門之上的 trial/force 動作模式 | -| `utils/action_effect/` | 110 | 判定一個動作是否真的產生效果,並歸因到目標區域 | +| `utils/act_modes/` | 69 | actionability 閘門之上的 trial/force 動作模式 | +| `utils/action_effect/` | 109 | 判定一個動作是否真的產生效果,並歸因到目標區域 | | `utils/action_grounding/` | 83 | 動作前的接地守衛(邊界檢查 + 吸附到元素) | -| `utils/actionability/` | 168 | 動作前就緒閘門(可見 + 穩定 + 啟用 + 未被遮擋) | +| `utils/actionability/` | 193 | 動作前就緒閘門(可見 + 穩定 + 啟用 + 未被遮擋) | | `utils/ensure_state/` | 74 | 冪等地把控制項/設定帶到期望狀態 | -| `utils/field_entry/` | 76 | 清空再輸入的欄位填寫慣用法(Playwright `fill`) | +| `utils/field_entry/` | 79 | 清空再輸入的欄位填寫慣用法(Playwright `fill`) | | `utils/gamepad/` | 333 | 虛擬遊戲手把後端(Windows ViGEmBus 驅動) | | `utils/humanize/` | 191 | 擬人輸入:貝茲曲線滑鼠路徑 + 抖動打字節奏 | -| `utils/ime_state/` | 146 | 讀取即時 IME 組字/轉換狀態,確保 CJK 輸入安全 | +| `utils/ime_state/` | 149 | 讀取即時 IME 組字/轉換狀態,確保 CJK 輸入安全 | | `utils/key_hold/` | 109 | 按住按鍵一段時間,或以固定頻率自動重複 | | `utils/modifier_state/` | 81 | 跨一組動作按住修飾鍵,並保證安全釋放 | -| `utils/mouse_path/` | 106 | 多路徑點滑鼠手勢(沿折線移動或拖曳) | +| `utils/mouse_path/` | 115 | 多路徑點滑鼠手勢(沿折線移動或拖曳) | | `utils/mouse_relative/` | 59 | 相對位移滑鼠移動 | -| `utils/postcondition/` | 146 | 宣告式的動作預期結果規格,對照畫面驗證 | +| `utils/postcondition/` | 157 | 宣告式的動作預期結果規格,對照畫面驗證 | | `utils/step_repair/` | 136 | 失敗/無效動作的修復策略(自我修正迴圈) | -| `utils/table_grid_fill/` | 163 | 以 OCR 文字填滿格線表格,取得可定址的表格 | +| `utils/table_grid_fill/` | 187 | 以 OCR 文字填滿格線表格,取得可定址的表格 | | `utils/input_reach/` | 111 | 送出去的輸入到不到得了:桌面鎖定查詢(免費)+ 實際送一個 F13 確認沒有被過濾(有副作用,只給診斷用) | | `utils/keyboard_layout/` | 152 | 向系統問「這個鍵盤配置下每個鍵印出什麼字」(`ToUnicodeEx`),問不到退回 US 對照表 | | `utils/text_unicode/` | 151 | 輸入任意 Unicode(emoji/CJK/重音字):優先送字元按鍵事件,不支援時退回剪貼簿貼上 | @@ -371,43 +371,43 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 ### 5.4.5 影像辨識與畫面分析 -> 37 個套件、約 5,782 行。 +> 37 個套件、約 6,064 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | -| `utils/annotate/` | 121 | 截圖標註:畫框、highlight、箭頭、標籤 | +| `utils/annotate/` | 122 | 截圖標註:畫框、highlight、箭頭、標籤 | | `utils/barcode/` | 59 | 一維條碼(EAN/UPC)解碼,解碼器可注入 | -| `utils/color_match/` | 127 | 在 HSV 通道上做顏色感知的樣板比對 | -| `utils/color_region/` | 96 | 以顏色定位畫面區域(遮罩 + 連通元件) | +| `utils/color_match/` | 151 | 在 HSV 通道上做顏色感知的樣板比對 | +| `utils/color_region/` | 107 | 以顏色定位畫面區域(遮罩 + 連通元件) | | `utils/color_stats/` | 103 | 區域顏色統計:平均色與主色 | | `utils/coordinate_space/` | 93 | 模型網格座標與實體像素之間的座標空間對映 | -| `utils/cv2_utils/` | 798 | OpenCV 基礎層:擷取後端選擇(`screen_grabber`,Pillow/mss 或平台後端)、截圖、樣板比對(走 `grab_logical`,涵蓋所有螢幕)、螢幕錄影、影片錄製(兩者都經 `frame_clock` 依 fps 配速)、連通元件、影像堆疊的取用口(`optional`,Windows arm64 沒有 wheel 時語意報錯)、非 ASCII 路徑也讀寫得到的影像檔存取(`image_file`) | -| `utils/edge_lines/` | 122 | 以 Hough 轉換偵測線條/格線/分隔線 | +| `utils/cv2_utils/` | 847 | OpenCV 基礎層:擷取後端選擇(`screen_grabber`,Pillow/mss 或平台後端)、截圖、`[left, top, right, bottom]` 區域擷取(`region_capture`,Windows 經 `grab_logical` 涵蓋所有螢幕)、樣板比對(走 `grab_logical`,涵蓋所有螢幕)、螢幕錄影、影片錄製(兩者都經 `frame_clock` 依 fps 配速)、連通元件、影像堆疊的取用口(`optional`,Windows arm64 沒有 wheel 時語意報錯)、非 ASCII 路徑也讀寫得到的影像檔存取(`image_file`) | +| `utils/edge_lines/` | 131 | 以 Hough 轉換偵測線條/格線/分隔線 | | `utils/edge_match/` | 115 | 邊緣形狀(Chamfer/距離轉換)樣板比對 | | `utils/feature_match/` | 143 | ORB 特徵比對:在旋轉/縮放/主題變更下定位樣板 | | `utils/hsv_segment/` | 104 | HSV 色彩空間分割(抗光照的顏色遮罩 + blob 框) | -| `utils/icon_classify/` | 132 | 從像素形狀判斷一個框是哪一類元件 | +| `utils/icon_classify/` | 151 | 從像素形狀判斷一個框是哪一類元件 | | `utils/image_dedup/` | 100 | 感知雜湊影像去重(Pillow aHash/dHash) | -| `utils/image_quality/` | 77 | 在 OCR/比對前評分影像品質(銳利度/對比/亮度) | +| `utils/image_quality/` | 82 | 在 OCR/比對前評分影像品質(銳利度/對比/亮度) | | `utils/img_histogram/` | 112 | 顏色直方圖指紋與變化偵測(抗光照) | -| `utils/marks_layout/` | 149 | Set-of-Marks 標籤的不重疊排版與可讀配色 | +| `utils/marks_layout/` | 175 | Set-of-Marks 標籤的不重疊排版與可讀配色 | | `utils/match_autothresh/` | 114 | Otsu 自動門檻,免去手動調 `min_score` | | `utils/match_ensemble/` | 67 | 多樣板共識比對(多張參考圖投票到同一位置) | | `utils/match_stability/` | 70 | 比對前的靜止閘門與跨影格的比對持續性 | -| `utils/match_trust/` | 144 | 樣板比對可信度評分(次峰比 + peak-to-sidelobe) | +| `utils/match_trust/` | 154 | 樣板比對可信度評分(次峰比 + peak-to-sidelobe) | | `utils/monitor_layout/` | 320 | 多螢幕/虛擬桌面幾何(在哪個螢幕、位置、重映射)+ `logical_frame` 以滑鼠座標空間擷取畫面 | -| `utils/motion_regions/` | 73 | 兩影格間的局部變化/活動偵測(absdiff) | -| `utils/perceptual_diff/` | 196 | 感知式(YIQ)影像差異,抑制反鋸齒邊緣誤報 | -| `utils/preprocess/` | 219 | OCR/比對前的影像前處理(灰階、二值化、去傾斜…) | +| `utils/motion_regions/` | 78 | 兩影格間的局部變化/活動偵測(absdiff) | +| `utils/perceptual_diff/` | 202 | 感知式(YIQ)影像差異,抑制反鋸齒邊緣誤報 | +| `utils/preprocess/` | 276 | OCR/比對前的影像前處理(灰階、二值化、去傾斜…) | | `utils/qr/` | 59 | 從影像或螢幕區域解碼 QR code(OpenCV) | -| `utils/rotated_match/` | 166 | 容忍旋轉與縮放的樣板比對(尺度空間 × 角度掃描) | +| `utils/rotated_match/` | 201 | 容忍旋轉與縮放的樣板比對(尺度空間 × 角度掃描) | | `utils/saliency/` | 114 | 頻譜殘差視覺顯著性:顯著圖與排序後的顯著區域 | -| `utils/scale_detect/` | 84 | 偵測樣板實際渲染的顯示縮放/視覺 DPI | +| `utils/scale_detect/` | 105 | 偵測樣板實際渲染的顯示縮放/視覺 DPI | | `utils/screen_grid/` | 146 | 供 VLM 接地用的粗粒度標號網格(點 ↔ 格對映) | -| `utils/set_of_marks/` | 154 | Set-of-Marks 疊圖:為畫面元素編號供 VLM 指認 | +| `utils/set_of_marks/` | 153 | Set-of-Marks 疊圖:為畫面元素編號供 VLM 指認 | | `utils/shape_locator/` | 108 | 以邊緣/輪廓偵測定位元件(矩形/形狀,免樣板) | -| `utils/ssim/` | 163 | 結構相似度比較:感知分數 + 變化區域 | -| `utils/subpixel_match/` | 103 | 以二次曲面擬合做次像素級比對精修 | +| `utils/ssim/` | 162 | 結構相似度比較:感知分數 + 變化區域 | +| `utils/subpixel_match/` | 109 | 以二次曲面擬合做次像素級比對精修 | | `utils/theme_normalize/` | 94 | 主題無關的影像正規化,讓亮色樣板能配對深色模式 | | `utils/video_report/` | 171 | 影片步驟疊圖報告:把截圖加字幕串成操作導覽影片 | | `utils/visual_match/` | 515 | 會回傳信心值的樣板比對(分數、多尺度、find-all + NMS);擷取走 `grab_logical`,命中座標已加回虛擬桌面原點,單色樣板直接拒收 | @@ -415,47 +415,47 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 ### 5.4.6 OCR 與文字理解 -> 19 個套件、約 3,469 行。 +> 19 個套件、約 3,576 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | | `utils/bidi_check/` | 138 | 雙向文字 QA(bidi 控制碼、巢狀平衡、Trojan-source 掃描) | -| `utils/column_layout/` | 153 | 從垂直空白推斷欄位,處理無框線表格 | +| `utils/column_layout/` | 167 | 從垂直空白推斷欄位,處理無框線表格 | | `utils/confusables/` | 146 | 易混淆/同形字偵測(Unicode 欺騙骨架) | | `utils/form_fields/` | 134 | 多方向關聯表單標籤與值,並讀取核取方塊狀態 | | `utils/fuzzy/` | 111 | 模糊字串比對與去重(預設 difflib,有 rapidfuzz 則優先) | -| `utils/grid_locator/` | 71 | 以 (row, column) 從邊界框定址表格/網格儲存格 | +| `utils/grid_locator/` | 100 | 以 (row, column) 從邊界框定址表格/網格儲存格 | | `utils/guardrail/` | 117 | 針對畫面/OCR 文字的啟發式 prompt-injection 防護 | | `utils/heading_segment/` | 71 | 判定 OCR 行是標題或內文,建出文件大綱 | | `utils/near_dup/` | 108 | 近似重複文字偵測(SimHash/MinHash) | | `utils/ocr/` | 1,140 | OCR 引擎門面 + 三個後端(Tesseract/EasyOCR/PaddleOCR)、版面結構化與跨詞比對(`text_span`) | | `utils/pii_text/` | 141 | 自由文字中的 PII 偵測與遮蔽(email/電話/SSN/卡號/IP/IBAN) | | `utils/readability/` | 140 | 可讀性評分(Flesch、Flesch-Kincaid、Gunning Fog、SMOG、ARI) | -| `utils/reading_flow/` | 145 | 以遞迴 XY-cut 推導欄位感知的閱讀順序 | -| `utils/search_index/` | 145 | 記憶體內 BM25/TF-IDF 全文檢索 | -| `utils/text_blocks/` | 88 | 把 OCR 行組成段落與項目符號/編號清單 | +| `utils/reading_flow/` | 165 | 以遞迴 XY-cut 推導欄位感知的閱讀順序 | +| `utils/search_index/` | 183 | 記憶體內 BM25/TF-IDF 全文檢索 | +| `utils/text_blocks/` | 89 | 把 OCR 行組成段落與項目符號/編號清單 | | `utils/text_diff/` | 202 | unified diff 產生、套用與三方合併 | | `utils/text_normalize/` | 84 | Unicode 正規化與 slug 產生 | -| `utils/text_regions/` | 163 | 免模型的畫面文字區域偵測(MSER):區域與行 | +| `utils/text_regions/` | 168 | 免模型的畫面文字區域偵測(MSER):區域與行 | | `utils/text_similarity/` | 172 | 字串距離度量(文字比對用) | ### 5.4.7 無障礙樹與原生控制項 -> 16 個套件、約 4,619 行。 +> 16 個套件、約 4,678 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | | `utils/a11y_audit/` | 362 | 以無障礙樹 + OCR 進行無障礙與 i18n 稽核 | -| `utils/accessibility/` | 3,117 | 跨平台無障礙樹定位與錄製;Windows UIA/macOS AX/null 三後端。支援限定視窗(換搜尋起點,不是過濾)、逐節點可中斷走訪、`IUIAutomation2` 連線逾時、名稱子字串比對與排序、`control_get_state` 一次讀完值/勾選/選取/數值(密碼欄位不回內容) | -| `utils/ax_events/` | 29 | 反應式 UIA 事件等待(focus-changed) | +| `utils/accessibility/` | 3,143 | 跨平台無障礙樹定位與錄製;Windows UIA/macOS AX/null 三後端。支援限定視窗(換搜尋起點,不是過濾)、逐節點可中斷走訪、`IUIAutomation2` 連線逾時、名稱子字串比對與排序、`control_get_state` 一次讀完值/勾選/選取/數值(密碼欄位不回內容) | +| `utils/ax_events/` | 35 | 反應式 UIA 事件等待(focus-changed) | | `utils/ax_props/` | 44 | 讀取豐富 UIA 屬性(enabled/offscreen/help/status/快捷鍵) | | `utils/ax_text/` | 102 | 透過 UIA TextPattern 取得原生文字(讀取/尋找/選取/屬性) | | `utils/ax_tree_walk/` | 119 | 可讀、可定址的無障礙樹後處理(角色名 + 節點路徑) | | `utils/contrast_map/` | 130 | 取樣實際顏色以評定畫面文字的可讀性(WCAG) | | `utils/control_patterns/` | 88 | 延伸 UIA 控制項模式動作(Expand/Select/Range/Scroll) | | `utils/cvd_simulate/` | 140 | 模擬色覺缺陷並標示在該狀況下會撞色的顏色 | -| `utils/element_repository/` | 113 | 原生 UI 元素的具名定位器倉庫(object repository) | -| `utils/focus_order/` | 95 | 鍵盤焦點順序:預期 Tab 序列、WCAG 稽核與設定焦點 | +| `utils/element_repository/` | 118 | 原生 UI 元素的具名定位器倉庫(object repository) | +| `utils/focus_order/` | 117 | 鍵盤焦點順序:預期 Tab 序列、WCAG 稽核與設定焦點 | | `utils/legacy_accessible/` | 45 | MSAA 橋接,處理 UIA 無法建模的舊控制項 | | `utils/selection_view/` | 57 | 容器選取狀態與檢視切換(Selection/MultipleView 模式) | | `utils/table_pattern/` | 65 | 原生表格的表頭與儲存格定址(UIA TablePattern/GridItem) | @@ -464,37 +464,37 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 ### 5.4.8 元素定位、自我修復與智慧等待 -> 23 個套件、約 4,316 行。 +> 23 個套件、約 4,426 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | | `utils/ab_locator/` | 386 | A/B 定位器框架:同時競速 N 種策略並記錄各自勝率 | | `utils/adaptive_timeout/` | 92 | 由觀測到的步驟耗時推導等待逾時,而非硬猜 | | `utils/anchor_locator/` | 476 | 錨點定位器:以空間關係組合 影像/OCR/VLM/a11y 四種來源 | -| `utils/app_idle/` | 109 | 等應用程式不再忙碌,再驅動下一步 | +| `utils/app_idle/` | 112 | 等應用程式不再忙碌,再驅動下一步 | | `utils/change_localize/` | 83 | 把畫面變化歸因到實際改變的元素框 | -| `utils/critic_features/` | 85 | 每步的 critic 特徵集合與規則式步驟評分 | +| `utils/critic_features/` | 88 | 每步的 critic 特徵集合與規則式步驟評分 | | `utils/element_diff/` | 94 | 跨影格的幾何感知元素比對(穩定 ID、移動追蹤) | -| `utils/element_parse/` | 106 | 融合並排序畫面元素框(IoU、合併、多來源融合、閱讀順序) | +| `utils/element_parse/` | 127 | 融合並排序畫面元素框(IoU、合併、多來源融合、閱讀順序) | | `utils/element_proposal/` | 92 | 免樣板、免模型地從原始像素提出乾淨元素清單 | | `utils/element_scoring/` | 105 | 加權候選評分(角色 + 名稱相似度 + 鄰近度 + 啟用狀態) | | `utils/expect_poll/` | 149 | 反覆取值直到符合條件(Playwright `expect.poll` 風格) | -| `utils/grounding_consensus/` | 153 | 對同一目標的多個接地提案做自我一致性投票 | +| `utils/grounding_consensus/` | 173 | 對同一目標的多個接地提案做自我一致性投票 | | `utils/heal_analytics/` | 77 | 自癒事件記錄的分析(治癒率、脆弱定位器) | | `utils/locator_chain/` | 112 | 可組合/可過濾的候選定位器(chained-locator 慣用法) | -| `utils/locator_repair/` | 117 | 自癒回寫:把修正後的定位器持久化 | -| `utils/observation/` | 92 | 供 VLM/agent 接地用的 token 預算內、帶索引的 a11y 文字觀察 | +| `utils/locator_repair/` | 147 | 自癒回寫:把修正後的定位器持久化 | +| `utils/observation/` | 89 | 供 VLM/agent 接地用的 token 預算內、帶索引的 a11y 文字觀察 | | `utils/observation_delta/` | 122 | token 預算內的觀察差異:兩個 UI 影格之間變了什麼 | | `utils/screen_state/` | 191 | 語義畫面狀態:快照/差異與結構化畫面描述 | | `utils/scroll_find/` | 103 | 捲動直到目標影像/文字可見 | | `utils/self_healing/` | 359 | 自癒定位器:先影像樣板、失敗改用 VLM,並留稽核記錄 | -| `utils/semantic_recording/` | 460 | 為錄製內容加上語義錨點,支援換機重播與自癒重播 | +| `utils/semantic_recording/` | 498 | 為錄製內容加上語義錨點,支援換機重播與自癒重播 | | `utils/settle_detector/` | 79 | 以純函式介面判定 UI 是否已靜止 | -| `utils/smart_waits/` | 674 | 智慧等待:以影格差異取代 `time.sleep` | +| `utils/smart_waits/` | 672 | 智慧等待:以影格差異取代 `time.sleep` | ### 5.4.9 AI / Agent / LLM -> 13 個套件、約 23,127 行。 +> 13 個套件、約 23,194 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | @@ -507,43 +507,43 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `utils/cua_action/` | 204 | 標準化 computer-use 動作結構(Anthropic/OpenAI → `AC_*`) | | `utils/llm/` | 365 | 自然語言 → action list 規劃器 + Anthropic/null 後端 | | `utils/mcp_registry/` | 97 | MCP registry `server.json` 資訊清單產生(可被發現) | -| `utils/mcp_server/` | 18,798 | **無頭 MCP 伺服器**(16K LOC,預設註冊 678 個工具=659 個 `ac_*` + 19 個別名):stdio + HTTP 傳輸、工具工廠與處理器、資源、prompt、稽核、限流、外掛熱重載 | +| `utils/mcp_server/` | 18,846 | **無頭 MCP 伺服器**(16K LOC,預設註冊 678 個工具=659 個 `ac_*` + 19 個別名):stdio + HTTP 傳輸、工具工廠與處理器、資源、prompt、稽核、限流、外掛熱重載 | | `utils/tool_use_schema/` | 195 | 把 `AC_*` 指令匯出成 Claude/OpenAI 的 tool-use schema | -| `utils/trajectory_eval/` | 113 | agent 軌跡評估:依評分規準為一次執行打分 | +| `utils/trajectory_eval/` | 132 | agent 軌跡評估:依評分規準為一次執行打分 | | `utils/vision/` | 538 | VLM 元素定位器(依描述找元素)+ Anthropic/OpenAI/null 後端 | ### 5.4.10 遠端桌面與 USB -> 6 個套件、約 19,332 行。 +> 6 個套件、約 19,487 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | | `utils/admin/` | 418 | 多主機管理主控台:平行輪詢 N 個 AutoControl REST 端點 | | `utils/config_sync/` | 332 | 透過訊令伺服器做跨機器設定同步 | -| `utils/device_matrix/` | 138 | 行動裝置矩陣:同一 action list 於多台裝置平行執行 | -| `utils/remote_desktop/` | 12,912 | **遠端桌面子系統**(56 檔/11.7K LOC):TCP/WebSocket/WebRTC 三條傳輸路徑、主機與檢視端、訊令伺服器、TURN/中繼、多檢視者、錄影、信任清單、TOTP、稽核鏈 | -| `utils/usb/` | 4,524 | 跨平台 USB 列舉/熱插拔/裝置直通(WinUSB、IOKit、libusb 後端 + ACL + WebRTC DataChannel 通道) | +| `utils/device_matrix/` | 143 | 行動裝置矩陣:同一 action list 於多台裝置平行執行 | +| `utils/remote_desktop/` | 13,014 | **遠端桌面子系統**(56 檔/11.7K LOC):TCP/WebSocket/WebRTC 三條傳輸路徑、主機與檢視端、訊令伺服器、TURN/中繼、多檢視者、錄影、信任清單、TOTP、稽核鏈 | +| `utils/usb/` | 4,572 | 跨平台 USB 列舉/熱插拔/裝置直通(WinUSB、IOKit、libusb 後端 + ACL + WebRTC DataChannel 通道) | | `utils/usbip/` | 1,008 | USB/IP 線路協定主機端(協定封包、TCP 伺服器、libusb URB 後端) | ### 5.4.11 伺服器、網路協定與外部整合 -> 24 個套件、約 6,721 行。 +> 24 個套件、約 7,035 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | | `utils/acme_v2/` | 617 | 完整 ACME v2 用戶端(RFC 8555),不依賴 certbot | | `utils/chatops/` | 667 | Chat-ops bot:接收 Slack/Discord/webhook 的 slash 指令並路由到動作 | -| `utils/cookie_jar/` | 122 | RFC 6265 cookie jar | +| `utils/cookie_jar/` | 191 | RFC 6265 cookie jar | | `utils/email_send/` | 118 | SMTP 寄信(email 觸發器的發送端搭檔) | | `utils/events/` | 106 | 對外 CloudEvents 發送(執行生命週期事件) | | `utils/http_cassette/` | 200 | 錄製/重播 HTTP 互動,做離線決定性 API 測試 | | `utils/http_client/` | 245 | 零依賴 HTTP(S) 用戶端,供 action 步驟呼叫 API | -| `utils/http_conditional/` | 115 | 條件式 HTTP 請求與快取驗證器 | +| `utils/http_conditional/` | 148 | 條件式 HTTP 請求與快取驗證器 | | `utils/http_content/` | 158 | HTTP 內容協商與回應解壓縮 | -| `utils/http_problem/` | 118 | RFC 9457 problem+json 解析 | -| `utils/jwt/` | 240 | JWT(HMAC 家族)編碼、解碼與 claim 驗證 | -| `utils/link_header/` | 150 | RFC 8288 Link header 解析與分頁 | -| `utils/multipart/` | 181 | multipart/form-data 建構與解析 | +| `utils/http_problem/` | 122 | RFC 9457 problem+json 解析 | +| `utils/jwt/` | 270 | JWT(HMAC 家族)編碼、解碼與 claim 驗證 | +| `utils/link_header/` | 176 | RFC 8288 Link header 解析與分頁 | +| `utils/multipart/` | 224 | multipart/form-data 建構與解析 | | `utils/notify/` | 106 | 跨平台桌面通知 | | `utils/notify_channels/` | 105 | 對外聊天/webhook 通知(Slack/Discord/Teams/raw) | | `utils/otp/` | 37 | TOTP 一次性密碼產生(自動化 2FA 登入) | @@ -551,75 +551,75 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `utils/pytest_plugin/` | 380 | pytest 外掛 + BDD step library(`pytest11` entry point) | | `utils/rest_api/` | 1,881 | 純標準庫 REST 前端:路由、Bearer 驗證、限流、Prometheus 指標、OpenAPI 3.1 產生 | | `utils/socket_server/` | 160 | 執行 action JSON 的執行緒式 TCP 指令伺服器(預設綁 127.0.0.1) | -| `utils/sse_client/` | 128 | Server-Sent Events 用戶端解析 | -| `utils/tls_acme/` | 455 | TLS 自動化:HTTP-01 挑戰伺服器、金鑰/CSR、自動續期 | -| `utils/url_canon/` | 156 | RFC 3986 URL 正規化與查詢字串工具 | -| `utils/webrunner_bridge/` | 169 | 把 action JSON 橋接到 WebRunner(`je_web_runner`) | +| `utils/sse_client/` | 142 | Server-Sent Events 用戶端解析 | +| `utils/tls_acme/` | 473 | TLS 自動化:HTTP-01 挑戰伺服器、金鑰/CSR、自動續期 | +| `utils/url_canon/` | 194 | RFC 3986 URL 正規化與查詢字串工具 | +| `utils/webrunner_bridge/` | 208 | 把 action JSON 橋接到 WebRunner(`je_web_runner`) | ### 5.4.12 報表、可觀測性與測試治理 -> 34 個套件、約 7,506 行。 +> 34 個套件、約 7,674 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | | `utils/anomaly/` | 114 | 單一序列異常偵測 | -| `utils/approval/` | 118 | Approval testing:以核可基準線驗證產出物 | +| `utils/approval/` | 131 | Approval testing:以核可基準線驗證產出物 | | `utils/assertion/` | 887 | 斷言 DSL:畫面狀態驗證 + 組合子 | -| `utils/baggage/` | 120 | W3C Baggage 傳遞 | +| `utils/baggage/` | 123 | W3C Baggage 傳遞 | | `utils/canonical_log/` | 96 | canonical log line 與結構化 JSON 日誌 | | `utils/ci_annotations/` | 65 | 由執行結果輸出 CI 工作流程註記(GitHub Actions) | -| `utils/compliance/` | 153 | 合規:把治理證據對應到 SOC2/ISO 27001 控制項 | +| `utils/compliance/` | 166 | 合規:把治理證據對應到 SOC2/ISO 27001 控制項 | | `utils/failure_hooks/` | 415 | 失敗 → 工單自動化:開 Jira/Linear/GitHub issue | | `utils/failure_signature/` | 76 | 把錯誤訊息正規化成穩定的 SHA-256 失敗簽章並分群 | -| `utils/flake_cluster/` | 103 | 以共同失敗 Jaccard 相似度為易碎測試分群 | -| `utils/flakiness/` | 150 | 以執行歷史分析不穩定測試 | -| `utils/generate_report/` | 293 | HTML/JSON/XML 三種報表產生器(Template Method) | -| `utils/media_assert/` | 242 | 媒體斷言:音訊活動與影片動態檢查 | +| `utils/flake_cluster/` | 112 | 以共同失敗 Jaccard 相似度為易碎測試分群 | +| `utils/flakiness/` | 151 | 以執行歷史分析不穩定測試 | +| `utils/generate_report/` | 294 | HTML/JSON/XML 三種報表產生器(Template Method) | +| `utils/media_assert/` | 273 | 媒體斷言:音訊活動與影片動態檢查 | | `utils/observability/` | 710 | Prometheus 格式指標 + OpenTelemetry 相容 trace + `/metrics` 匯出伺服器 | -| `utils/otlp_export/` | 109 | OTLP/JSON span 匯出 | +| `utils/otlp_export/` | 114 | OTLP/JSON span 匯出 | | `utils/percentiles/` | 119 | 可合併的串流延遲摘要與精確百分位數 | | `utils/process_doc/` | 108 | 由錄製的 action list 產生逐步 SOP 文件 | -| `utils/process_mining/` | 123 | 流程探勘:從動作日誌挖掘可自動化的候選 | +| `utils/process_mining/` | 131 | 流程探勘:從動作日誌挖掘可自動化的候選 | | `utils/profiler/` | 451 | 逐動作效能剖析器 + 資源剖析器 | -| `utils/quarantine/` | 200 | 易碎測試隔離區,讓套件執行器跳過已知不穩定案例 | -| `utils/run_diff/` | 123 | 兩次執行軌跡的差異(LCS 對齊:新增/移除/狀態翻轉/退化) | -| `utils/run_history/` | 439 | 執行歷史儲存與產出物管理 | +| `utils/quarantine/` | 204 | 易碎測試隔離區,讓套件執行器跳過已知不穩定案例 | +| `utils/run_diff/` | 143 | 兩次執行軌跡的差異(LCS 對齊:新增/移除/狀態翻轉/退化) | +| `utils/run_history/` | 449 | 執行歷史儲存與產出物管理 | | `utils/sarif/` | 187 | 以 SARIF 2.1.0 匯出發現項,供 GitHub/Azure code scanning | | `utils/slo/` | 115 | SLO 評估:SLI、錯誤預算與多視窗燃燒率告警 | | `utils/smoothing/` | 67 | 數列移動平均平滑 | | `utils/soft_assert/` | 79 | 軟斷言:累積檢查並在區塊結束時一次拋出 | | `utils/stats/` | 236 | 描述統計與 A/B 顯著性檢定(純標準庫) | -| `utils/step_timeline/` | 81 | 每次執行的步驟瀑布圖與瓶頸(關鍵路徑)步驟排名 | -| `utils/test_select/` | 129 | 以執行歷史做風險導向的測試選取 | -| `utils/test_shard/` | 105 | 以耗時為權重的套件切分與分片結果合併 | +| `utils/step_timeline/` | 87 | 每次執行的步驟瀑布圖與瓶頸(關鍵路徑)步驟排名 | +| `utils/test_select/` | 131 | 以執行歷史做風險導向的測試選取 | +| `utils/test_shard/` | 106 | 以耗時為權重的套件切分與分片結果合併 | | `utils/test_suite/` | 547 | QA 套件編排:把扁平 action list 評分為測試案例 + CI 報表 | | `utils/time_travel/` | 388 | 錄製 session 的時光回溯除錯(控制器 + 播放器) | | `utils/timeseries/` | 175 | 時間序列轉換(rate/降採樣/重採樣) | -| `utils/trace_context/` | 183 | W3C Trace Context 傳遞 | +| `utils/trace_context/` | 224 | W3C Trace Context 傳遞 | ### 5.4.13 資料來源、結構驗證與 i18n -> 24 個套件、約 4,662 行。 +> 24 個套件、約 5,196 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | -| `utils/checksum/` | 138 | 檢查碼演算法:Luhn、Verhoeff、Damm、ISO 7064 MOD 97-10 | -| `utils/config_schema/` | 130 | 型別化設定結構驗證 | -| `utils/data_drift/` | 128 | 分布漂移偵測 | -| `utils/data_profile/` | 129 | 資料剖析與結構推斷 | -| `utils/data_quality/` | 218 | 資料品質:列結構驗證、欄位擷取、遮蔽 | +| `utils/checksum/` | 160 | 檢查碼演算法:Luhn、Verhoeff、Damm、ISO 7064 MOD 97-10 | +| `utils/config_schema/` | 144 | 型別化設定結構驗證 | +| `utils/data_drift/` | 143 | 分布漂移偵測 | +| `utils/data_profile/` | 169 | 資料剖析與結構推斷 | +| `utils/data_quality/` | 232 | 資料品質:列結構驗證、欄位擷取、遮蔽 | | `utils/data_source/` | 235 | 資料驅動執行:從 CSV/JSON/SQLite/Excel 載入資料列 | -| `utils/dataset_diff/` | 89 | 表格資料列差異比對(CDC 風格) | -| `utils/gettext_catalog/` | 362 | GNU gettext 目錄 I/O(解析 .po、編譯/讀取 .mo、訊息查詢) | -| `utils/i18n_test/` | 231 | 國際化/在地化測試輔助 | -| `utils/json_contract/` | 145 | JSON 契約/快照比對:`match_json`、`diff_json`、`snapshot_json` | +| `utils/dataset_diff/` | 116 | 表格資料列差異比對(CDC 風格) | +| `utils/gettext_catalog/` | 371 | GNU gettext 目錄 I/O(解析 .po、編譯/讀取 .mo、訊息查詢) | +| `utils/i18n_test/` | 228 | 國際化/在地化測試輔助 | +| `utils/json_contract/` | 158 | JSON 契約/快照比對:`match_json`、`diff_json`、`snapshot_json` | | `utils/json_patch/` | 352 | JSON Pointer(6901)、JSON Patch(6902)與 Merge Patch(7386) | -| `utils/json_schema/` | 426 | JSON Schema(Draft 2020-12 子集)驗證 | -| `utils/jsonpath/` | 322 | 精簡 JSONPath 查詢 | +| `utils/json_schema/` | 666 | JSON Schema(Draft 2020-12 子集)驗證 | +| `utils/jsonpath/` | 360 | 精簡 JSONPath 查詢 | | `utils/list_format/` | 82 | 地區感知清單格式化(CLDR 風格的「A、B 和 C」) | -| `utils/locale_collation/` | 135 | 地區感知字串排序(決定性多層排序鍵) | +| `utils/locale_collation/` | 139 | 地區感知字串排序(決定性多層排序鍵) | | `utils/locale_parse/` | 80 | 地區感知數字/貨幣/日期解析與格式化(選用 babel) | -| `utils/message_format/` | 288 | ICU-lite MessageFormat(plural/select/selectordinal) | +| `utils/message_format/` | 389 | ICU-lite MessageFormat(plural/select/selectordinal) | | `utils/office/` | 198 | Office 文件無頭讀寫(Excel/Word/PowerPoint) | | `utils/pdf/` | 117 | PDF 讀取與斷言(選用 pypdf 後端) | | `utils/referential/` | 83 | 跨資料集的參照完整性檢查 | @@ -630,7 +630,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 ### 5.4.14 安全、機密與合規 -> 13 個套件、約 2,913 行。 +> 13 個套件、約 2,964 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | @@ -639,10 +639,10 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `utils/governance/` | 242 | 治理:maker-checker 核准閘門與即時憑證租約 | | `utils/license_policy/` | 240 | 以 SBOM 元件評估 SPDX 授權允許/拒絕政策 | | `utils/provenance/` | 126 | SLSA 建置來源證明(in-toto v1) | -| `utils/rbac/` | 299 | 角色型存取控制:使用者、角色與權杖驗證(尚未接到 REST/MCP) | +| `utils/rbac/` | 302 | 角色型存取控制:使用者、角色與權杖驗證(尚未接到 REST/MCP) | | `utils/redaction/` | 508 | 截圖遮蔽層:規則偵測 + 政策 + 協調器(上傳 VLM 前先遮) | -| `utils/sbom/` | 148 | SBOM(CycloneDX)產生 | -| `utils/secret_ref/` | 143 | URI scheme 形式的值參照解析 | +| `utils/sbom/` | 170 | SBOM(CycloneDX)產生 | +| `utils/secret_ref/` | 169 | URI scheme 形式的值參照解析 | | `utils/secrets/` | 360 | 加密機密儲存庫,供 `${secrets.NAME}` 解析 | | `utils/secrets_scan/` | 138 | 掃描 action JSON/資料中應入庫卻硬編碼的機密 | | `utils/vex/` | 178 | OpenVEX 陳述撰寫與漏洞分類處置 | @@ -650,28 +650,28 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 ### 5.4.15 韌性、流量控制與設定 -> 14 個套件、約 2,033 行。 +> 14 個套件、約 2,159 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | | `utils/artifact_store/` | 148 | S3 相容產出物儲存(報表/截圖/錄影) | -| `utils/assets/` | 178 | 環境範圍的型別化資產/設定儲存(UiPath Assets 風格) | +| `utils/assets/` | 203 | 環境範圍的型別化資產/設定儲存(UiPath Assets 風格) | | `utils/bulkhead/` | 141 | Bulkhead 併發隔離 + 伺服器限流標頭解析 | | `utils/chaos/` | 153 | 決定性混沌實驗(穩態假說 + 故障注入) | | `utils/dedup_window/` | 72 | 時間視窗內的訊息去重 | -| `utils/dotenv/` | 165 | `.env` 檔解析與序列化 | +| `utils/dotenv/` | 222 | `.env` 檔解析與序列化 | | `utils/feature_flags/` | 191 | 功能旗標評估,含目標規則與決定性灰度 | | `utils/idempotency/` | 142 | 冪等鍵儲存與已存回應重放 | -| `utils/layered_config/` | 110 | 分層設定解析 | +| `utils/layered_config/` | 114 | 分層設定解析 | | `utils/optimistic/` | 135 | 樂觀併發的版本化儲存 | -| `utils/rate_limit/` | 204 | 用戶端限流:token bucket、滑動視窗、throttle | +| `utils/rate_limit/` | 233 | 用戶端限流:token bucket、滑動視窗、throttle | | `utils/resilience/` | 146 | 韌性原語:退避重試與斷路器 | -| `utils/retry_budget/` | 158 | 重試預算:以牆鐘期限與 full jitter 約束重試 | +| `utils/retry_budget/` | 169 | 重試預算:以牆鐘期限與 full jitter 約束重試 | | `utils/sequence_gap/` | 90 | 逐串流的序號缺口偵測 | ### 5.4.16 系統、視窗與剪貼簿 -> 16 個套件、約 2,610 行。 +> 16 個套件、約 2,616 行。 | 模組 | 行數 | 職責 | | --- | ---: | --- | @@ -688,7 +688,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `utils/system_volume/` | 212 | 讀取與控制系統主音量與靜音狀態 | | `utils/trash/` | 93 | 把檔案移到系統資源回收筒(可復原刪除) | | `utils/window_capture/` | 304 | 逐視窗截圖、視窗版面儲存/還原、貼齊與排列 | -| `utils/window_geometry/` | 81 | 視窗客戶區幾何(外框內縮、client→screen 對映) | +| `utils/window_geometry/` | 87 | 視窗客戶區幾何(外框內縮、client→screen 對映) | | `utils/window_layout/` | 137 | 視窗拼貼/版面規劃器(左右半、四象限、網格、層疊) | | `utils/window_zorder/` | 76 | 視窗 z 序控制(最上層/移到最前/送到最後) | @@ -696,42 +696,43 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 上表以子套件為單位;以下把行數最大的幾個子系統展開到檔案層。 -#### `utils/executor/`(9,504 行)— 執行核心 +#### `utils/executor/`(9,560 行)— 執行核心 | 檔案 | 行數 | 職責 | | --- | ---: | --- | -| `action_executor.py` | 8,308 | `Executor` 類別與 `event_dict` 分派表(775 個指令),另含數百個把 utils 能力接成指令的 adapter 函式;全域單例 `executor` 與 `add_command_to_executor()` 擴充點。 | -| `flow_control.py` | 643 | 真正的流程控制:`AC_loop`/`AC_for_each`/`AC_while_*`/`AC_if_*`/`AC_try`/`AC_retry`/`AC_parallel`/`AC_define_macro`/`AC_call_macro`/變數指令(`AC_set_var`/`AC_get_var`/`AC_inc_var`)。`LoopBreak`/`LoopContinue` 以例外實作。34 個區塊指令的分派表 `BLOCK_COMMANDS` 也在這裡,含下一列匯入的資料來源指令。 | +| `action_executor.py` | 8,341 | `Executor` 類別與 `event_dict` 分派表(776 個指令),另含數百個把 utils 能力接成指令的 adapter 函式;全域單例 `executor` 與 `add_command_to_executor()` 擴充點。 | +| `flow_control.py` | 644 | 真正的流程控制:`AC_loop`/`AC_for_each`/`AC_while_*`/`AC_if_*`/`AC_try`/`AC_retry`/`AC_parallel`/`AC_define_macro`/`AC_call_macro`/變數指令(`AC_set_var`/`AC_get_var`/`AC_inc_var`)。`LoopBreak`/`LoopContinue` 以例外實作。34 個區塊指令的分派表 `BLOCK_COMMANDS` 也在這裡,含下一列匯入的資料來源指令。 | | `flow_data_commands.py` | 272 | `AC_*_to_var` 資料來源與轉換指令:shell、時鐘、亂數、PDF、TOTP、SQL、檔案、HTTP、OCR,加上 `AC_assert_var`/`AC_assert_db`/`AC_assert_duration`/`AC_transform_var`。都不執行巢狀 action list,所以沒有迴圈/分支語意。 | | `action_schema.py` | 159 | action list 的結構驗證:形狀、參數型別、未知指令拒絕。單一走訪同時支援兩種消費方式:`validate_actions()` 遇到第一個問題就拋、`unknown_command_names()` 收齊全部不認得的名字(REST `/execute` 用它回 400)。 | -| `action_redaction.py` | 83 | 記錄與紀錄鍵用的遮蔽:`AC_secret_*` 的參數(金庫通行碼、機密值)在寫進 log、當成結果紀錄的鍵之前換成 `***`,巢狀在區塊指令裡的也一樣。 | +| `action_redaction.py` | 86 | 記錄與紀錄鍵用的遮蔽:`AC_secret_*` 的參數(金庫通行碼、機密值)在寫進 log、當成結果紀錄的鍵之前換成 `***`,巢狀在區塊指令裡的也一樣。 | | `mouse_aliases.py` | 39 | 單鍵點擊別名(`AC_click_left` 等),executor 與 callback executor 共用。 | +| `flags.py` | 19 | 旗標讀取 `as_bool`:依拼法(`"true"`/`"yes"`/`"on"`/`"1"`)而非 Python 真值判斷,所以 JSON、CLI、MCP 傳來的 `"false"` 是關;所有 adapter 與流程指令共用。 | -#### `utils/mcp_server/`(18,798 行,678 個工具)— 最大子系統 +#### `utils/mcp_server/`(18,846 行,678 個工具)— 最大子系統 | 檔案 | 行數 | 職責 | | --- | ---: | --- | -| `tools/_factories.py` | 9,023 | 工具工廠:每個函式回傳一個領域的 `MCPTool` 清單(把 `AC_*` 能力包成 MCP 工具)。 | +| `tools/_factories.py` | 9,029 | 工具工廠:每個函式回傳一個領域的 `MCPTool` 清單(把 `AC_*` 能力包成 MCP 工具)。 | | `tools/_handlers.py` | 545 | 把 MCP 工具呼叫橋接到 AutoControl 無頭 API 的 adapter;主題模組拆完之後這裡留的是資料/文字/HTTP 那一類與 WebRunner 橋接。 | | `tools/_handlers_qa.py` | 419 | 同一種 adapter,QA 主題:斷言 DSL、資料驅動、SQL/PDF/郵件/HTTP 步驟、codegen、視覺回歸、狀態機、flaky 偵測與隔離、suite runner、無障礙稽核、裝置矩陣、媒體斷言。從 `_handlers.py` 依主題拆出的第一塊(750 行上限);兩者互不引用。 | | `tools/_handlers_input.py` | 218 | 同一種 adapter,輸入主題:滑鼠、鍵盤、虛擬手把(ViGEm)。 | -| `tools/_handlers_screen.py` | 327 | 同一種 adapter,螢幕主題:擷取、像素、影像與文字搜尋、螢幕錄影。 | +| `tools/_handlers_screen.py` | 331 | 同一種 adapter,螢幕主題:擷取、像素、影像與文字搜尋、螢幕錄影。 | | `tools/_handlers_system.py` | 575 | 同一種 adapter,桌面工作階段:視窗、行程與 shell、開檔、閒置與睡眠、音量、鎖定、輸入法狀態、欄位驗證與重試、色彩對比、變更排序、元件分類、剪貼簿。 | | `tools/_handlers_runs.py` | 110 | 同一種 adapter,執行主題:executor、執行歷史、錄製、動作檔。 | | `tools/_handlers_scheduling.py` | 200 | 同一種 adapter,排程主題:排程器、觸發器、熱鍵常駐。 | | `tools/_handlers_remote.py` | 66 | 同一種 adapter,遠端桌面的 host 與 viewer。 | | `tools/_handlers_executor_bridge.py` | 1,429 | 252 個純委派(中位數 3 行,最長的 16 行全是參數簽章):每個都是 `from action_executor import _x` 再 `return _x(...)`,沒有分支邏輯。超過 750 行,理由記在 `Progress.md` 的豁免表(再切只能照 MCP 工廠領域分,會把同一種委派散進十幾個沒有語意邊界的檔)。 | | `tools/_handlers_locators.py` | 436 | 同一種 adapter,定位主題:無障礙樹、智慧等待、自我修復、螢幕觀察、座標空間、視覺與 OCR、影像去重、元件倉庫、A/B 定位。 | -| `tools/_handlers_operations.py` | 647 | 同一種 adapter,營運主題:agent 與其記憶/追蹤、治理與合規、成本與遙測、失敗掛鉤、看門狗、速率限制、檢查點、核可、產物與資產、測試選擇與分片、佇列與 saga。 | -| `server.py` | 700 | JSON-RPC 2.0 over stdio 的最小 MCP 伺服器:連線範圍狀態、行內/併發分派、工具與 resource/prompt 處理器;握手時代的方法表(`_run_method`),兩個協定時代的逐請求分派在 `_stateless.py`。 | -| `http_transport.py` | 715 | MCP 的 HTTP 傳輸;宣告 2026-07-28 的請求走 `_http_stateless.py` 的標頭規則,不發 session。 | +| `tools/_handlers_operations.py` | 629 | 同一種 adapter,營運主題:agent 與其記憶/追蹤、治理與合規、成本與遙測、失敗掛鉤、看門狗、速率限制、檢查點、核可、產物與資產、測試選擇與分片、佇列與 saga。 | +| `server.py` | 721 | JSON-RPC 2.0 over stdio 的最小 MCP 伺服器:連線範圍狀態、行內/併發分派、工具與 resource/prompt 處理器;握手時代的方法表(`_run_method`),兩個協定時代的逐請求分派在 `_stateless.py`。 | +| `http_transport.py` | 719 | MCP 的 HTTP 傳輸;宣告 2026-07-28 的請求走 `_http_stateless.py` 的標頭規則,不發 session。 | | `_http_stateless.py` | 185 | MCP 2026-07-28 在 Streamable HTTP 上的規則:`MCP-Protocol-Version`/`Mcp-Method`/`Mcp-Name` 必須與 body 相符(`=?base64?…?=` 先解碼),不符是 400+`HeaderMismatch`;版本與中繼資料錯誤 400、未知方法 404。純函式,由 `http_transport.py` 回覆。 | | `http_sessions.py` | 247 | MCP 的 HTTP 傳輸用的 session 身分:`Mcp-Session-Id` 註冊表,以及每個 session 那條常駐的 server→client SSE 串流。 | | `_client_requests.py` | 254 | 伺服器主動送出的請求:`roots/list`/`elicitation/create`/`sampling/createMessage`,對應表與回應路由,以及破壞性工具的確認交握。只屬於握手時代:無狀態請求裡送出會丟例外。 | | `_stateless.py` | 267 | MCP 2026-07-28 無狀態版本,與以 `initialize` 握手的版本並存:逐請求的 `_meta`(版本、client 能力、`logLevel`)、`server/discover`、結果的 `resultType`/`serverInfo`/快取提示、`-32020`~`-32022` 錯誤碼,以及兩個時代逐請求分派的 mixin。 | | `_input_required.py` | 160 | 多輪往返請求(MRTR):`input_required` 結果,與 HMAC 簽章、會過期、只兌換一次的 `requestState`;破壞性工具確認在無狀態請求裡的形式。 | | `_subscriptions.py` | 256 | 變更通知:握手時代的 `resources/subscribe`/`unsubscribe` 與未經訂閱的 `resources/updated`、`tools/list_changed`(不送給無狀態的對端);2026-07-28 的 `subscriptions/listen`:確認、以訂閱 id 標記的通知、取消與伺服器結束時的完成回覆。 | -| `_protocol.py` | 219 | JSON-RPC 線路格式:版本與識別常數、`_MCPError`、決定失敗工具行為的錯誤 tuple、envelope 產生器、工具回傳值轉 `content` 區塊。不碰伺服器狀態。 | +| `_protocol.py` | 243 | JSON-RPC 線路格式:版本與識別常數、`_MCPError`、決定失敗工具行為的錯誤 tuple、envelope 產生器、工具回傳值轉 `content` 區塊。不碰伺服器狀態。 | | `resources.py` | 307 | MCP resource 提供者。 | | `prompts.py` | 220 | MCP prompt 目錄。 | | `fake_backend.py` | 184 | CI/無頭測試用的記憶體內假後端。 | @@ -740,29 +741,29 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `tools/_validation.py` | 122 | MCP 工具用到的 JSON Schema 子集驗證器。 | | `tools/plugin_tools.py` | 89 | 把外掛載入的 `AC_*` callable 包成 `MCPTool`。 | | `log_bridge.py` | 118 | 把 Python logging 記錄橋接成 MCP `notifications/message`;2026-07-28 的請求只收到自己設了 `logLevel` 時產生的記錄。 | -| `audit.py` | 87 | MCP 工具呼叫稽核記錄。 | +| `audit.py` | 94 | MCP 工具呼叫稽核記錄。 | | `context.py` | 71 | 傳給 opt-in 工具處理器的每次呼叫上下文。 | | `rate_limit.py` | 48 | 工具呼叫的 token bucket 限流。 | | `__main__.py` | 92 | `je_auto_control_mcp` console script 進入點。 | -#### `utils/remote_desktop/`(12,912 行/56 檔) +#### `utils/remote_desktop/`(13,014 行/56 檔) 三條傳輸路徑並存:**TCP**(JPEG 影格)、**WebSocket**(同協定換傳輸)、**WebRTC**(aiortc 視訊 + DataChannel)。 | 檔案 | 行數 | 職責 | | --- | ---: | --- | -| `webrtc_host.py` | 716 | WebRTC 主機:串流螢幕視訊並接受檢視端輸入;session 生命週期、DataChannel 接線、檔案收發。 | +| `webrtc_host.py` | 722 | WebRTC 主機:串流螢幕視訊並接受檢視端輸入;session 生命週期、DataChannel 接線、檔案收發。 | | `webrtc_viewer.py` | 677 | WebRTC 檢視端:接收視訊並送出輸入。 | -| `host.py` | 669 | TCP 主機:接受迴圈、TLS 包裝、連線/認證握手、音訊與剪貼簿廣播、檔案推送、單次 token。 | +| `host.py` | 673 | TCP 主機:接受迴圈、TLS 包裝、連線/認證握手、音訊與剪貼簿廣播、檔案推送、單次 token。 | | `viewer.py` | 634 | TCP 檢視端。 | -| `host_service.py` | 558 | 無頭 WebRTC 主機執行器 + 多平台服務安裝器。 | +| `host_service.py` | 562 | 無頭 WebRTC 主機執行器 + 多平台服務安裝器。 | | `host_client.py` | 453 | TCP 主機的每連線處理器:一個檢視端一個實例,擁有它的認證交換、sender/audio/receiver 三條執行緒,以及入站訊息的路由表。 | | `registry.py` | 370 | `AC_remote_*` 指令使用的行程級單例。 | -| `webrtc_transport.py` | 411 | 共用 WebRTC 管線:asyncio 橋接執行緒、螢幕視訊軌、設定。 | -| `multi_viewer.py` | 339 | 每個連入檢視端各跑一個 `WebRTCDesktopHost` 的協調器。 | +| `webrtc_transport.py` | 421 | 共用 WebRTC 管線:asyncio 橋接執行緒、螢幕視訊軌、設定。 | +| `multi_viewer.py` | 357 | 每個連入檢視端各跑一個 `WebRTCDesktopHost` 的協調器。 | | `signaling_server.py` | 427 | 獨立的 WebRTC SDP 交換 rendezvous 服務。 | | `audit_log.py` | 355 | SQLite 雜湊鏈稽核記錄。 | -| `host_capture.py` | 297 | TCP 主機的影格與游標產生:螢幕列舉、監視器索引轉擷取區域、預設 JPEG/游標 provider,以及 `FrameProductionMixin`(游標輪詢、擷取迴圈、上線編碼)。 | +| `host_capture.py` | 323 | TCP 主機的影格與游標產生:螢幕列舉、監視器索引轉擷取區域、預設 JPEG/游標 provider,以及 `FrameProductionMixin`(游標輪詢、擷取迴圈、上線編碼)。 | | `ws_protocol.py` | 318 | 最小 RFC 6455 WebSocket 框架與握手。 | | `file_transfer.py` | 371 | 分塊檔案傳輸。 | | `relay.py` | 315 | NAT 穿透失敗時的 TCP 中繼。 | @@ -784,9 +785,9 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `signaling_client.py` | 164 | 純標準庫的訊令用戶端。 | | `trust_list.py` | 139 | 自動接受的檢視端信任清單。 | | `webrtc_inspector.py` | 138 | 行程級的 `StatsSnapshot` 滾動視窗。 | -| `input_dispatch.py` | 141 | 在主機端套用輸入訊息。 | +| `input_dispatch.py` | 161 | 在主機端套用輸入訊息。 | | `session_recorder.py` | 139 | 以 PyAV 把 WebRTC 影格錄成 mp4。 | -| `totp.py` | 146 | RFC 6238 TOTP(零外部相依)。 | +| `totp.py` | 160 | RFC 6238 TOTP(零外部相依)。 | | `file_sync.py` | 141 | 輪詢式資料夾鏡像。 | | `transport.py` | 126 | 可插拔的型別化訊息傳輸。 | | `host_access.py` | 112 | TCP 主機的檢視端核准與存取控制:`PendingViewer`、權限字串、分享碼的 TOTP 候選值、IP 白名單。`host` 與 `host_client` 共用,所以獨立成模組。 | @@ -796,7 +797,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `permissions.py` / `clipboard_sync.py` / `wake_on_lan.py` / `session_actions.py` / `auth.py` | 64 / 74 / 56 / 40 / 28 | 逐 session 權限、剪貼簿同步、WOL、SAS 注入與螢幕遮蔽、HMAC 挑戰回應。 | | `ws_host.py` / `ws_viewer.py` / `jpeg_recorder.py` | 40 / 29 / 146 | WebSocket 傳輸變體與 TCP 路徑錄影。 | -#### `utils/usb/`(4,524 行)與 `utils/usbip/`(1,008 行) +#### `utils/usb/`(4,572 行)與 `utils/usbip/`(1,008 行) | 檔案 | 行數 | 職責 | | --- | ---: | --- | @@ -813,7 +814,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `usb/passthrough/key_provider.py` | 125 | ACL 的可插拔 HMAC 金鑰來源。 | | `usb/passthrough/commands.py` | 150 | 無頭直通指令(單一真實來源)。 | | `usb/usb_devices.py` | 296 | 跨平台 USB 裝置列舉。 | -| `usb/usb_watcher.py` | 260 | 輪詢式 USB 熱插拔監看。 | +| `usb/usb_watcher.py` | 308 | 輪詢式 USB 熱插拔監看。 | | `usbip/protocol.py` | 342 | USB/IP 線路格式封裝/解析。 | | `usbip/server.py` | 295 | USB/IP 主機端 TCP 伺服器。 | | `usbip/libusb_backend.py` | 224 | 以 PyUSB/libusb 執行 URB 的正式後端。 | @@ -850,7 +851,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。 | `semantic_recording/` | `enrich.py`(加錨點)、`replay.py`(換機重播)、`self_healing.py`(自癒重播) | | `tls_acme/` | `challenge.py`、`keys.py`、`renewal.py` | | `pytest_plugin/` | `plugin.py`(pytest11 進入點)、`keywords.py`、`bdd_steps.py`(Gherkin) | -| `cv2_utils/` | `screen_grabber.py`、`screenshot.py`、`template_detection.py`、`screen_record.py`、`video_recording.py`、`frame_clock.py`、`blobs.py`、`optional.py`、`image_file.py` | +| `cv2_utils/` | `screen_grabber.py`、`screenshot.py`、`region_capture.py`、`template_detection.py`、`screen_record.py`、`video_recording.py`、`frame_clock.py`、`blobs.py`、`optional.py`、`image_file.py` | | `action_lint/` | `linter.py`、`schema.py`、`__main__.py`(CI 使用) | | `time_travel/` | `controller.py`、`player.py` | | `dag/` | `graph.py`、`runner.py` | @@ -880,20 +881,21 @@ GUI 是**選用 extra**(`pip install je_auto_control[gui]`,PySide6 + qt-mate | 模組 | 行數 | 職責 | | --- | ---: | --- | | `gui/__init__.py` | 23 | `start_autocontrol_gui()`:**唯一**會延遲匯入 PySide6 的地方,維持頂層套件 Qt-free。 | -| `main_window.py` | 293 | `QMainWindow`:選單列(File/Actions/View/…)、可關閉分頁、即時語言切換、字級預設、qt-material 主題。分頁分為 core/editing/detection/automation/system 五類。 | -| `main_widget.py` | 430 | 擁有 `QTabWidget`,註冊 48 個分頁,並暴露 show/hide/list API 給選單列。核心分頁在註冊時直接宣告 `(label_key, handler)` 動作對;分頁本體都在下列 mixin。 | -| `_auto_click_tab.py` | 286 | 自動點擊分頁的 mixin 建構器。 | +| `main_window.py` | 301 | `QMainWindow`:選單列(File/Actions/View/…)、可關閉分頁、即時語言切換、字級預設、qt-material 主題。分頁分為 core/editing/detection/automation/system 五類。 | +| `main_widget.py` | 437 | 擁有 `QTabWidget`,註冊 48 個分頁,並暴露 show/hide/list API 給選單列。核心分頁在註冊時直接宣告 `(label_key, handler)` 動作對;分頁本體都在下列 mixin。 | +| `_auto_click_tab.py` | 291 | 自動點擊分頁的 mixin 建構器。 | | `_screenshot_tab.py` | 137 | 截圖/取像素分頁 mixin。 | | `_image_detect_tab.py` | 115 | 影像偵測分頁 mixin。 | | `_script_tab.py` | 115 | 腳本執行分頁 mixin。 | -| `_record_tab.py` | 110 | 錄製/回放分頁 mixin。 | +| `_record_tab.py` | 114 | 錄製/回放分頁 mixin。 | | `_report_tab.py` | 88 | 報表分頁 mixin。 | | `_i18n_helpers.py` | 66 | 需要即時語言切換的分頁共用的翻譯註冊 mixin。 | | `_validators.py` | 29 | `int_validator()`/`double_validator()`:以 C locale 驗證的數字輸入框 validator,接受的正是 `int()`/`float()` 讀得懂的寫法(預設 locale 在法文、德文下只收小數逗號)。所有數字 `QLineEdit` 都用它。 | +| `_screen_geometry.py` | 52 | Qt 邏輯座標與截圖用的原生像素互轉:`native_region()`、`screen_at_native()`、`logical_point()`(每個螢幕的左上角在兩者相同,螢幕內依 device pixel ratio 縮放)。區域選取與主機端標註覆蓋層都用它。 | | `_daemon_thread.py` | 79 | `DaemonThread`:`QThread` 的替代品,保留遠端桌面 worker 用到的介面(`start`/`run`/`isRunning`/`wait`/`requestInterruption`/`started`/`finished`),但 `run()` 跑在 daemon `threading.Thread` 上,刪除物件或程式結束都不會銷毀執行中的執行緒。 | -| `_worker_thread.py` | 192 | `start_worker()`:在 daemon `threading.Thread` 上執行 `QObject` worker 的 `run()`(沒有 `QThread` 可被銷毀),並經由分頁擁有的中繼物件回報結果(回呼一律在 GUI 執行緒;worker 沒處理的例外也送到 `on_fail`);worker 留在模組登錄表直到 GUI 執行緒看到它結束,回傳 `WorkerHandle`(`isRunning()`);程式結束時先呼叫 worker 的 `request_stop()`,最多等 10 秒,仍在跑的隨行程結束。 | +| `_worker_thread.py` | 216 | `start_worker()`:在 daemon `threading.Thread` 上執行 `QObject` worker 的 `run()`(沒有 `QThread` 可被銷毀),並經由分頁擁有的中繼物件回報結果(回呼一律在 GUI 執行緒;worker 沒處理的例外也送到 `on_fail`);worker 留在模組登錄表直到 GUI 執行緒看到它結束,回傳 `WorkerHandle`(`isRunning()`);程式結束時先呼叫 worker 的 `request_stop()`,最多等 10 秒,仍在跑的隨行程結束。 | | `language_wrapper/` | 5,031 | 四語系字典(英/日/簡中/繁中)+ `multi_language_wrapper` 執行期切換器與監聽註冊表。 | -| `selector/` | 227 | 拖曳選取螢幕區域的半透明全螢幕覆蓋層與樣板裁切工具(互動式,但都有對應的程式化 API)。 | +| `selector/` | 216 | 拖曳選取螢幕區域的半透明全螢幕覆蓋層與樣板裁切工具(互動式,但都有對應的程式化 API)。 | > **分頁指令一律走 Actions 選單**:分頁本身只放輸入、表格與結果檢視,指令由視窗層選單暴露。 > 核心分頁在 `main_widget.py` 註冊時宣告動作;功能分頁實作 `menu_actions()`(目前 40 個檔案有此 hook)。 @@ -952,7 +954,7 @@ GUI 是**選用 extra**(`pip install je_auto_control[gui]`,PySide6 + qt-mate | diagnostics | `diagnostics_tab.py` | 91 | 執行子系統檢查並顯示結果。 | | report | `_report_tab.py` | 81 | 產生 HTML/JSON/XML 報表。 | -#### 遠端桌面 GUI(`gui/remote_desktop/`,19 檔/6,608 行) +#### 遠端桌面 GUI(`gui/remote_desktop/`,19 檔/6,646 行) | 模組 | 行數 | 職責 | | --- | ---: | --- | @@ -970,7 +972,7 @@ GUI 是**選用 extra**(`pip install je_auto_control[gui]`,PySide6 + qt-mate | `_helpers.py` | 249 | 面板共用輔助:翻譯、Qt→AC 鍵滑鼠對應、TLS context、狀態徽章、指紋與時間格式化。 | | `remote_screen_window.py` | 140 | 檢視端的彈出視窗。 | | `tray_icon.py` | 106 | WebRTC 主機的系統匣圖示。 | -| `annotation_overlay.py` | 136 | 主機端標註的透明最上層覆蓋。 | +| `annotation_overlay.py` | 174 | 主機端標註的透明最上層覆蓋。 | | `sparkline.py` | 77 | WebRTC 統計面板的迷你走勢圖。 | | `blanking_overlay.py` | 71 | 遠端連線期間的隱私遮蔽全螢幕覆蓋。 | | `viewer_screen_window.py` | 46 | 顯示連入檢視端分享畫面的彈出視窗。 | @@ -989,6 +991,7 @@ GUI 是**選用 extra**(`pip install je_auto_control[gui]`,PySide6 + qt-mate | `docker/` | Dockerfile ×8 + compose + 9 支驗證/伺服器腳本 | 無頭容器(`Dockerfile`)、帶 XFCE 桌面的容器(`Dockerfile.xfce`),以及四個**驗證用**映像:`Dockerfile.wayland`(sway headless,擷取路徑 + `libei_verify.py` 對真的 libei.so 解析符號)、`Dockerfile.eis`(`eis_server.py` 用 ctypes 綁 libeis 起一個真的 EIS server,`eis_verify.py` 把 libei sender 對著它跑完整握手與發送)、`Dockerfile.portal`(`portal_server.py` 自己佔住 `org.freedesktop.portal.Desktop`,真的 `dbus-daemon` + 真的 liboeffis 跑完 RemoteDesktop 交握)、`Dockerfile.ydotool`(真的 uinput 裝置,`ydotool_verify.py` 直接讀回 `/dev/input/eventN`)、`Dockerfile.seat`(`headless,libinput` + builtin seat,合成器真的吃下 ydotool 裝置,`seat_verify.py` 從 `grim -c` 的像素讀回游標落點)、`Dockerfile.x11`(真的 Xvfb + openbox,`x11_verify.py` 用 `xev` 把注入的事件從真的客戶端讀回(含 `synthetic NO`,這是 XTest 跟 `XSendEvent` 的差別),另用 ImageMagick `import` 做獨立擷取對照,跑兩種螢幕版面)。全部接在 `.github/workflows/docker.yml`。 | | `k8s/helm/` | Helm chart | Kubernetes 部署。 | | `ci_templates/.gitlab-ci.yml` | — | 供使用者專案複製的 GitLab CI 範本。 | +| `scripts/dev_release.py` | 只用標準函式庫的發佈輔助腳本 | dev 通道(PyPI 的 `je_auto_control_dev`)的版本與比對:`prepare` 把 `dev.toml` 寫成 `pyproject.toml`,版本取 PyPI 最新一版加一個 patch(`dev.toml` 的版本只是下限,不回寫 repo);`changed dist` 把建好的 wheel 跟 PyPI 最新一版逐檔比對,內容沒變就不上傳。只給 `dev.yml` 的 `publish-dev` job 與拋棄式的 checkout 用,因為 `prepare` 會蓋掉 `pyproject.toml`。 | | `docs/` | Sphinx(`API`/`Eng`/`Zh`/`getting_started`) | Read the Docs 文件。 | | `architecture_diagram/` | drawio + png | 既有的架構圖原始檔。 | | `test/` | `unit_test/headless`(主要)、`unit_test/flow_control`、`integrated_test`、`gui_test`、`manual_test`、`verify`、`test_source` | 478 個 `test_*.py`/4,654 個測試函式。**注意**:`test/unit_test/` 下的 `*_test.py` 是會真的驅動滑鼠鍵盤的手動示範腳本,因此 `pyproject.toml` 把 `python_files` 釘成 `test_*.py`。`unit_test/headless/conftest.py` 有一個 autouse fixture,每個測試結束都沖掉 Qt 排隊中的 `deleteLater()`——不沖會讓殘留的 widget 在後面某個不相干的測試裡被銷毀,曾經整個直譯器 `__fastfail`。`test_doc_counts.py` 守住文件引用的指令/工具/子套件/範例數,`test_doc_line_counts.py` 守住所有行數(`--fix` 可一次重新產生)。 `verify/macos_verify.py` 是在真的 `macos-14` runner 上量測 TCC 到底允許什麼的探針(macOS 是唯一沒有容器可用的支援平台),不被 pytest 收集。 | @@ -1016,6 +1019,7 @@ GUI 是**選用 extra**(`pip install je_auto_control[gui]`,PySide6 + qt-mate | 檔案 | 用途 | | --- | --- | | `stable.yml` | 每次 push/PR 到 `main` 與每日排程跑 Windows 五版本的示範腳本;合併到 main 後版本遞增並上傳 PyPI(使用 `PYPI_API_TOKEN`)。 | +| `dev.yml` | 每次 push/PR 到 `dev` 跑 headless pytest(四格:Windows 的 3.10 與 3.14、Ubuntu 3.14、macOS 3.10,都是 `quality.yml` 九格裡的格子,不量 coverage)。push 通過後由 `publish-dev` job 用 `dev.toml` 建置並上傳 `je_auto_control_dev`(同一個 `PYPI_API_TOKEN`);只有這個 commit 仍是 `dev` 的最新一筆、而且 wheel 內容跟 PyPI 最新一版不同時才上傳,版本由 `scripts/dev_release.py` 向 PyPI 查,不回寫 repo。 | | `release.yml` | 發佈流程(上傳步驟目前關閉)。 | | `quality.yml` | ruff、bandit、dependency review、九格矩陣的 headless pytest(含 coverage 地板)與 mypy。 | | `platform-smoke.yml` | 跨平台煙霧測試。 | @@ -1068,26 +1072,26 @@ socket 預設綁 `127.0.0.1`;資源一律用 `with`。 | 層/子系統 | 檔案數 | 行數 | | --- | ---: | ---: | -| `gui/` | 94 | 27,525 | -| `utils/mcp_server/` | 35 | 18,798 | -| `utils/remote_desktop/` | 56 | 12,912 | -| `utils/executor/` | 7 | 9,504 | -| `utils/usb/` | 17 | 4,524 | -| `je_auto_control/`(頂層 3 檔) | 3 | 2,410 | -| `utils/accessibility/` | 14 | 3,117 | -| `wrapper/` | 19 | 3,631 | +| `gui/` | 95 | 27,829 | +| `utils/mcp_server/` | 35 | 18,846 | +| `utils/remote_desktop/` | 56 | 13,014 | +| `utils/executor/` | 8 | 9,560 | +| `utils/usb/` | 17 | 4,572 | +| `je_auto_control/`(頂層 3 檔) | 3 | 2,411 | +| `utils/accessibility/` | 14 | 3,143 | +| `wrapper/` | 19 | 3,667 | | `windows/` | 23 | 1,959 | | `utils/rest_api/` | 8 | 1,881 | | `utils/agent/` | 9 | 1,975 | | `linux_with_x11/` | 19 | 1,281 | | `linux_wayland/` | 17 | 2,921 | -| `utils/triggers/` | 4 | 1,377 | +| `utils/triggers/` | 4 | 1,383 | | `utils/ocr/` | 9 | 1,140 | | `utils/usbip/` | 5 | 1,008 | | `utils/assertion/` | 3 | 887 | | `osx/` | 17 | 925 | | `autocontrol-lsp/` | 8 | 744 | -| `utils/hotkey/` | 7 | 846 | -| 其餘模組(約 286 個 `utils/` 子套件 + `android/`/`ios/`/周邊小工具) | 679 | 54,959 | -| **總計** | **1,053** | **154,324** | +| `utils/hotkey/` | 7 | 852 | +| 其餘模組(約 286 個 `utils/` 子套件 + `android/`/`ios/`/周邊小工具) | 681 | 57,038 | +| **總計** | **1,057** | **157,036** | diff --git a/dev.toml b/dev.toml index 9ced4fb33..2bd513bc9 100644 --- a/dev.toml +++ b/dev.toml @@ -1,5 +1,11 @@ -# Rename to build dev version -# This is dev version +# The dev channel, je_auto_control_dev. The publish-dev job of .github/workflows/dev.yml builds it by +# writing this file to pyproject.toml (scripts/dev_release.py). The version below is a floor: CI publishes +# one patch above the newest release on PyPI and commits nothing back. +# +# Everything that decides what the package installs and ships is kept identical to pyproject.toml: +# [build-system], requires-python, dependencies, extras, scripts, entry points and [tool.setuptools]. +# The reason for each pin and marker is in the comments there, not repeated here. +# test/unit_test/headless/test_dev_toml_parity.py fails when the two files differ. [build-system] requires = ["setuptools>=82.0.1"] build-backend = "setuptools.build_meta" @@ -14,15 +20,22 @@ description = "GUI Automation Framework" requires-python = ">=3.10" license-files = ["LICENSE"] dependencies = [ - "je_open_cv", - "pillow", - "pyobjc-core;platform_system=='Darwin'", - "pyobjc;platform_system=='Darwin'", - "python-Xlib;platform_system=='Linux'", - "mss" + "je_open_cv==0.0.22; sys_platform != 'win32' or platform_machine != 'ARM64'", + "opencv-python>=4.8,<6; sys_platform != 'win32' or platform_machine != 'ARM64'", + "pillow==12.3.0", + "pyobjc-core==12.2.1;platform_system=='Darwin'", + "pyobjc==12.2.1;platform_system=='Darwin'", + "python-Xlib==0.33;platform_system=='Linux' or platform_system=='FreeBSD' or platform_system=='OpenBSD' or platform_system=='NetBSD'", + "mss==10.2.0", + "defusedxml==0.7.1", + "cryptography>=48.0.1; sys_platform != 'win32' or platform_machine != 'ARM64'" ] classifiers = [ "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Development Status :: 2 - Pre-Alpha", "Environment :: Win32 (MS Windows)", "Environment :: MacOS X", @@ -30,6 +43,13 @@ classifiers = [ "Operating System :: OS Independent" ] +[project.scripts] +je_auto_control = "je_auto_control.cli:main" +je_auto_control_mcp = "je_auto_control.utils.mcp_server.__main__:main" + +[project.entry-points.pytest11] +je_auto_control = "je_auto_control.utils.pytest_plugin.plugin" + [project.urls] Homepage = "https://github.com/Intergration-Automation-Testing/AutoControl" Documentation = "https://autocontrol.readthedocs.io/en/latest/" @@ -42,5 +62,24 @@ content-type = "text/markdown" [tool.setuptools.packages] find = { namespaces = false } +[tool.setuptools.package-data] +"je_auto_control" = ["py.typed"] +"je_auto_control.utils.remote_desktop" = [ + "web_viewer/*.html", + "web_viewer/*.js", + "web_viewer/*.svg", + "web_viewer/*.webmanifest", + "web_viewer/mic-worklet.js", +] + [project.optional-dependencies] -gui = ["PySide6==6.11.1", "qt-material"] +gui = ["PySide6==6.11.1", "qt-material==2.17"] +webrtc = ["aiortc>=1.14.0", "av>=14.0.0"] +signaling = ["fastapi>=0.115", "starlette>=1.0.1", "uvicorn>=0.32"] +discovery = ["zeroconf>=0.149.16"] +pdf = ["pypdf>=6.16.1"] +office = ["openpyxl>=3.1", "python-docx>=1.1", "python-pptx>=0.6"] +fuzzy = ["rapidfuzz>=3.0"] +s3 = ["boto3>=1.34"] +locale = ["babel>=2.12"] +audio = ["pycaw>=20240210"] diff --git a/docs/source/API/utils/package_manager.rst b/docs/source/API/utils/package_manager.rst index f3fa093d1..1b211d46d 100644 --- a/docs/source/API/utils/package_manager.rst +++ b/docs/source/API/utils/package_manager.rst @@ -45,6 +45,18 @@ PackageManager :param predicate: Inspection predicate (e.g., ``isfunction``, ``isclass``). :param target: Target executor whose ``event_dict`` will be updated. + .. method:: allow_packages(*packages) + + Adds packages, and their submodules, to the package gate's allowlist. + + .. method:: set_allow_arbitrary_packages(enabled) + + Allows (``True``) or refuses (``False``) packages outside the allowlist. Until either switch + is called, any package loads with a ``DeprecationWarning``. ``add_package_to_executor`` and + ``add_package_to_callback_executor`` check the gate before importing and raise + ``AutoControlExecuteActionException`` for a refused package. The ``Executor`` has the same two + static methods; neither is an ``AC_*`` command. + .. method:: add_package_to_target(package, target) Loads functions, built-ins, and classes from a package into the specified target executor. diff --git a/docs/source/Eng/doc/keyboard/keyboard_doc.rst b/docs/source/Eng/doc/keyboard/keyboard_doc.rst index 3f2afb1ea..6de8673c7 100644 --- a/docs/source/Eng/doc/keyboard/keyboard_doc.rst +++ b/docs/source/Eng/doc/keyboard/keyboard_doc.rst @@ -72,6 +72,21 @@ Type a sequence of characters one by one: write("Hello World") +``write`` logs the text, records it in the test record and returns it. For a +password or token use ``write_secret`` (``AC_write_secret`` with ``secret``): +the log gets the length only, the record a masked value, it returns nothing, and +an error never names a character. It types every character as a Unicode key +event, so the text arrives exactly (``write`` types capitals as lower case on +Windows); a backend without Unicode typing (only Windows has it) refuses before +typing anything. + +.. code-block:: python + + import os + from je_auto_control import write_secret + + write_secret(os.environ["APP_PASSWORD"]) + Hotkey Combinations =================== diff --git a/docs/source/Eng/doc/keyword_and_executor/keyword_and_executor_doc.rst b/docs/source/Eng/doc/keyword_and_executor/keyword_and_executor_doc.rst index 05386f1a7..79d2ad399 100644 --- a/docs/source/Eng/doc/keyword_and_executor/keyword_and_executor_doc.rst +++ b/docs/source/Eng/doc/keyword_and_executor/keyword_and_executor_doc.rst @@ -27,6 +27,11 @@ For example: ["AC_write", {"write_string": "Hello"}] ] +An on / off parameter (``ignore_case``, ``present``, ``raise_on_fail``, ``paste`` …) +takes a JSON ``true`` / ``false``, or a string read by its spelling: ``"true"``, +``"yes"``, ``"on"`` and ``"1"`` are on, and any other string (``"false"``, ``"no"``, +``"off"``, ``"0"``) is off. + Available Action Commands ========================= @@ -39,7 +44,7 @@ Available Action Commands * - Mouse - ``AC_click_mouse``, ``AC_set_mouse_position``, ``AC_get_mouse_position``, ``AC_press_mouse``, ``AC_release_mouse``, ``AC_mouse_scroll`` * - Keyboard - - ``AC_type_keyboard``, ``AC_press_keyboard_key``, ``AC_release_keyboard_key``, ``AC_write``, ``AC_hotkey``, ``AC_check_key_is_press`` + - ``AC_type_keyboard``, ``AC_press_keyboard_key``, ``AC_release_keyboard_key``, ``AC_write``, ``AC_write_secret``, ``AC_hotkey``, ``AC_check_key_is_press`` * - Image - ``AC_locate_all_image``, ``AC_locate_image_center``, ``AC_locate_and_click`` * - Screen @@ -78,6 +83,22 @@ Extending the Executor You can dynamically load external Python packages into the executor: +The package gate decides which packages may load. ``AC_add_package_to_executor`` can import ``os`` or +``subprocess`` for any action list, so the host program lists what it needs: + +.. code-block:: python + + from je_auto_control import executor + + executor.allow_packages("time") # these, and their submodules + executor.set_allow_arbitrary_packages(False) # refuse everything else before importing it + +Neither switch is an ``AC_*`` command, so an action list cannot open its own gate; a refused package fails +its action with ``AutoControlExecuteActionException``. Until the host calls either switch, any package still +loads but raises a ``DeprecationWarning``; a future release will refuse packages outside the allowlist by +default. + + .. code-block:: python from je_auto_control import package_manager diff --git a/docs/source/Eng/doc/mcp_server/mcp_server_doc.rst b/docs/source/Eng/doc/mcp_server/mcp_server_doc.rst index 9dba6643b..bf0622cda 100644 --- a/docs/source/Eng/doc/mcp_server/mcp_server_doc.rst +++ b/docs/source/Eng/doc/mcp_server/mcp_server_doc.rst @@ -104,6 +104,15 @@ are refused before the tool runs, as a tool execution error: a result with corrected arguments (MCP 2025-11-25). An unknown tool or a request that is not a ``tools/call`` at all is still a ``-32602`` protocol error. +A tool that fails with any exception is answered the same way, ``isError: true`` +with the error's type and message, so no call is left without a reply. A tool +that ran is not reported as failed because the audit log could not be written; +that is logged instead. A message that is not a JSON-RPC 2.0 request -- no or a +wrong ``jsonrpc``, a ``method`` that is not a string, an ``id`` that is not a +string, a number or ``null`` -- is ``-32600``. A request with ``"id": null`` is +answered with ``"id": null``; only a message without an ``id`` member is a +notification. + Resources, prompts, sampling ============================ diff --git a/docs/source/Eng/doc/new_features/v113_features_doc.rst b/docs/source/Eng/doc/new_features/v113_features_doc.rst index fe0d24189..4032004d9 100644 --- a/docs/source/Eng/doc/new_features/v113_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v113_features_doc.rst @@ -37,7 +37,14 @@ count placeholder, a plural ``offset:`` (``#`` becomes count − offset), nested arguments, and ICU apostrophe quoting (``''`` → ``'``; ``'{'`` → literal brace; ``'#'`` only inside a plural, elsewhere the apostrophes stay). ``plural_rules`` / ``ordinal_rules`` let you inject custom category functions; -``locale`` selects the built-ins (``en``, ``fr``). +``locale`` selects the rules by its language (``fr_FR`` and ``fr-CA`` use +``fr``): English and French are built in, any other locale uses Babel's CLDR +data when Babel is installed (``je_auto_control[locale]``) and raises +otherwise instead of falling back to English. ``=N`` selectors compare as +numbers (``=1.0`` matches 1). A pattern ICU rejects -- an unterminated +argument, a selector without ``{...}``, no ``other``, a duplicate selector, +``offset:`` anywhere but first -- raises ``MessageFormatError`` (an +``AutoControlException`` and a ``ValueError``). Executor commands ----------------- diff --git a/docs/source/Eng/doc/new_features/v114_features_doc.rst b/docs/source/Eng/doc/new_features/v114_features_doc.rst index b5ba22d20..0d98563eb 100644 --- a/docs/source/Eng/doc/new_features/v114_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v114_features_doc.rst @@ -35,8 +35,10 @@ Headless API ``ngettext`` evaluates the catalog's ``Plural-Forms`` expression (via ``gettext.c2py``) to pick the right form for ``n``; ``pgettext`` adds a disambiguation context. ``to_mo_bytes`` / ``compile_mo`` emit a standards- -compliant ``.mo`` that Python's own ``gettext.GNUTranslations`` can load, and -``read_mo`` / ``read_mo_file`` parse one back (little- or big-endian). +compliant ``.mo`` that Python's own ``gettext.GNUTranslations`` can load, +leaving out untranslated entries (an empty first ``msgstr``) and fuzzy ones as +``msgfmt`` does, so every reader falls back to the ``msgid``; ``read_mo`` / +``read_mo_file`` parse one back (little- or big-endian). Executor commands ----------------- diff --git a/docs/source/Eng/doc/new_features/v116_features_doc.rst b/docs/source/Eng/doc/new_features/v116_features_doc.rst index 3b39a672f..5355aad6d 100644 --- a/docs/source/Eng/doc/new_features/v116_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v116_features_doc.rst @@ -31,7 +31,9 @@ steps (``easing`` is any name from ``path_easings()`` — ``linear`` / ``ease_in_out_quad`` / ``ease_out_cubic`` / ``ease_in_cubic``) and does not duplicate the shared junction points. ``move_along_path`` emits move events through the path; ``drag_path`` presses at the first waypoint, moves through the -whole path, and releases at the last — for multi-stop drags. Both take a ``sink`` +whole path, and releases at the last — for multi-stop drags. If a move fails +partway, the button is released where the pointer stopped, so an aborted drag +does not drop at its target. Both take a ``sink`` override for headless testing. Executor commands diff --git a/docs/source/Eng/doc/new_features/v117_features_doc.rst b/docs/source/Eng/doc/new_features/v117_features_doc.rst index e36f20c53..fff2fabcf 100644 --- a/docs/source/Eng/doc/new_features/v117_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v117_features_doc.rst @@ -29,7 +29,8 @@ Headless API # {'op': 'key', 'key': 'delete'}, # {'op': 'type', 'text': 'hi'}] -``clear`` is ``"select_all"`` (the ``modifier``+A then Delete clear) or +``clear`` is ``"select_all"`` (the ``modifier``+A then Delete clear; Backspace on +macOS, whose key table has no Delete) or ``"none"``. ``paste=True`` enters the text through the clipboard (``modifier``+V) — the reliable path for Unicode / emoji / CJK that ``write`` cannot type — rather than typing key by key. ``modifier`` is the platform command key (``"ctrl"``; use diff --git a/docs/source/Eng/doc/new_features/v11_features_doc.rst b/docs/source/Eng/doc/new_features/v11_features_doc.rst index 892b475cb..634c06ffe 100644 --- a/docs/source/Eng/doc/new_features/v11_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v11_features_doc.rst @@ -68,6 +68,8 @@ store, then run the riskiest first (or only the top-k):: risky = select_flows(["login", "checkout", "report"], k=2) The score is ``0.5*failure_rate + 0.2*last_failed + 0.2*flakiness + -0.1*staleness``; a never-run flow scores ``0.8`` (untested is risky). +0.1*staleness`` over the newest ``window`` finished runs (runs still marked +running are not counted); a never-run flow scores ``0.8`` (untested is risky), +and a flow listed twice is ranked once. Exposed as ``AC_rank_tests`` / ``AC_select_tests`` and the ``ac_rank_tests`` / ``ac_select_tests`` MCP tools. diff --git a/docs/source/Eng/doc/new_features/v125_features_doc.rst b/docs/source/Eng/doc/new_features/v125_features_doc.rst index ccfa8ee9b..8c8dcb6c2 100644 --- a/docs/source/Eng/doc/new_features/v125_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v125_features_doc.rst @@ -35,7 +35,9 @@ cell, or ``{found: False, reason}`` when the index is out of range. Executor commands ----------------- -``AC_grid_cell`` takes ``boxes`` (a JSON ``[[x, y, w, h], ...]`` list, e.g. from a -prior ``AC_locate_all_image`` step) plus ``row`` / ``col`` / ``row_tolerance`` and -returns the cell dict. It is exposed as the MCP tool ``ac_grid_cell`` and as a +``AC_grid_cell`` takes ``boxes`` (a JSON ``[[x, y, w, h], ...]`` list) plus ``row`` / +``col`` / ``row_tolerance`` and returns the cell dict. ``AC_locate_all_image`` answers +``[left, top, right, bottom]`` boxes, so pass its output with ``"box_format": "ltrb"`` +(``cluster_grid`` / ``locate_cell`` take the same ``box_format``; dicts and OCR match +objects are read by their fields). It is exposed as the MCP tool ``ac_grid_cell`` and as a Script Builder command under **Mouse**. diff --git a/docs/source/Eng/doc/new_features/v128_features_doc.rst b/docs/source/Eng/doc/new_features/v128_features_doc.rst index efe84a211..d409b4865 100644 --- a/docs/source/Eng/doc/new_features/v128_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v128_features_doc.rst @@ -30,7 +30,10 @@ Headless API ``find_color_regions`` returns ``{x, y, width, height, area, center}`` for each blob within ``tolerance`` (per channel) of ``rgb`` and at least ``min_area`` pixels, largest first; ``find_color_region`` returns just the largest (or -``None``). ``haystack`` defaults to a screen grab of the optional ``region``. +``None``). ``haystack`` defaults to a screen grab of the optional ``region``, +``[left, top, right, bottom]`` on any monitor. Blobs found in a grabbed region +are in screen coordinates, so ``center`` can be clicked as it is; blobs in a +supplied ``haystack`` are in that image's pixels. Executor commands ----------------- diff --git a/docs/source/Eng/doc/new_features/v12_features_doc.rst b/docs/source/Eng/doc/new_features/v12_features_doc.rst index bbc473c53..a70471f53 100644 --- a/docs/source/Eng/doc/new_features/v12_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v12_features_doc.rst @@ -30,7 +30,7 @@ at every call site, and a UI change is fixed in one place:: 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 +``app_name``; a blank value filters nothing and is refused); 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. diff --git a/docs/source/Eng/doc/new_features/v139_features_doc.rst b/docs/source/Eng/doc/new_features/v139_features_doc.rst index 877ed9907..0ec8ca4f4 100644 --- a/docs/source/Eng/doc/new_features/v139_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v139_features_doc.rst @@ -31,7 +31,9 @@ Headless API ``val_min`` floor to skip greys, returning ``{x, y, width, height, area, center}`` per blob largest first — so it finds a colour at any brightness, unlike the RGB box. ``segment_hsv`` takes an explicit ``lower_hsv`` / ``upper_hsv`` band; ``color_mask`` -returns the raw uint8 mask. +returns the raw uint8 mask. ``region`` is ``[left, top, right, bottom]`` on any +monitor, and blobs found in it are in screen coordinates; blobs in a supplied +``haystack`` are in that image's pixels. Executor commands ----------------- diff --git a/docs/source/Eng/doc/new_features/v13_features_doc.rst b/docs/source/Eng/doc/new_features/v13_features_doc.rst index 61b3c2f71..f674c1099 100644 --- a/docs/source/Eng/doc/new_features/v13_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v13_features_doc.rst @@ -28,6 +28,9 @@ runs, instead of re-deriving the steps every time:: lib.search("auth") # find skills by name / description / tags lib.run("login") # replay through the executor +A skill that runs skills is bounded like a macro: nesting deeper than 50 fails +the outermost ``AC_skill_run`` instead of recursing. + Executor / MCP commands: ``AC_skill_save`` / ``AC_skill_run`` / ``AC_skill_list`` / ``AC_skill_remove`` / ``AC_skill_search`` (and the matching ``ac_skill_*`` MCP tools). This is the durable counterpart to the diff --git a/docs/source/Eng/doc/new_features/v141_features_doc.rst b/docs/source/Eng/doc/new_features/v141_features_doc.rst index 8bf7b2f2e..741db1207 100644 --- a/docs/source/Eng/doc/new_features/v141_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v141_features_doc.rst @@ -35,7 +35,9 @@ longest first; pass ``orientation`` other than ``any`` to keep only that kind. ``find_grid`` clusters the horizontal rules into row coordinates and the vertical rules into columns, returning ``{rows, cols, cells}`` (cells are the rectangles between consecutive rules). ``find_separators`` returns the merged coordinates of long divider -lines along ``axis``. A blank screen yields no lines / cells. +lines along ``axis``. A blank screen yields no lines / cells. Lines down to +``min_length`` are found (the Hough vote threshold follows it), and 16-bit or +float images are read as 8-bit. Executor commands ----------------- diff --git a/docs/source/Eng/doc/new_features/v152_features_doc.rst b/docs/source/Eng/doc/new_features/v152_features_doc.rst index e44af5182..f0b689abd 100644 --- a/docs/source/Eng/doc/new_features/v152_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v152_features_doc.rst @@ -31,7 +31,9 @@ Headless API click(*[target["x"] + target["width"] // 2, target["y"] + target["height"] // 2]) ``flatten_tree`` flattens a nested element tree, keeping only interactive roles by -default. ``observation_index`` clips to the ``viewport``, orders top-to-bottom / +default (the same roles, in any platform's spelling, as ``focus_order``). Elements +are placed by ``x`` / ``y`` / ``width`` / ``height`` or ``bounds``. ``observation_index`` +clips to the ``viewport`` (an element centred on its right or bottom edge is outside), orders top-to-bottom / left-to-right, caps at ``max_elements`` and assigns a stable ``index``. ``serialize_observation`` renders those as ``[i] role "name" @(cx,cy)`` lines. diff --git a/docs/source/Eng/doc/new_features/v167_features_doc.rst b/docs/source/Eng/doc/new_features/v167_features_doc.rst index 8939ab539..1ebd63804 100644 --- a/docs/source/Eng/doc/new_features/v167_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v167_features_doc.rst @@ -36,7 +36,7 @@ Headless API ``classify_effect`` returns an ``EffectVerdict`` (``effect`` / ``changed_near_target`` / ``changed_count`` / ``changed_centers`` / ``reason``). ``effect_near_point`` answers whether any -change landed within ``radius`` of an arbitrary point; ``is_no_op`` is the boolean shortcut. +change landed within ``radius`` (a circle) of an arbitrary point; ``is_no_op`` is the boolean shortcut. Executor commands ----------------- diff --git a/docs/source/Eng/doc/new_features/v171_features_doc.rst b/docs/source/Eng/doc/new_features/v171_features_doc.rst index f22e58bd2..5811b5dd9 100644 --- a/docs/source/Eng/doc/new_features/v171_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v171_features_doc.rst @@ -29,7 +29,8 @@ Headless API ``match_subpixel`` returns a ``SubPixelMatch`` (integer ``x`` / ``y`` / ``width`` / ``height`` / ``score`` plus float ``cx`` / ``cy`` and the ``offset_x`` / ``offset_y`` applied), or ``None`` -below ``min_score``. ``refine_peak`` returns the ``[-0.5, 0.5]`` quadratic-fit offset of a peak +below ``min_score``. ``cx`` is the matched box's centre in pixel indices, ``x + (width - 1) / 2``, +plus the offset. 16-bit and float images are matched in 8 bits. ``refine_peak`` returns the ``[-0.5, 0.5]`` quadratic-fit offset of a peak from its neighbours — usable on any correlation surface. Executor command diff --git a/docs/source/Eng/doc/new_features/v177_features_doc.rst b/docs/source/Eng/doc/new_features/v177_features_doc.rst index f1c074c02..ae787644b 100644 --- a/docs/source/Eng/doc/new_features/v177_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v177_features_doc.rst @@ -34,7 +34,8 @@ Headless API report when a spec is given. ``score_step_rule_based`` returns ``{outcome, process_score, reasons}`` — ``outcome`` is a binary success (the action did something *and* any postcondition held), ``process_score`` is a 0..1 quality from the effect class (halved if the postcondition -failed). ``to_judge_prompt`` renders the record for an external judge. +failed). An effect other than ``no_op`` / ``changed`` / ``changed_near_target`` / +``changed_elsewhere`` raises ``ValueError``. ``to_judge_prompt`` renders the record for an external judge. Executor commands ----------------- diff --git a/docs/source/Eng/doc/new_features/v184_features_doc.rst b/docs/source/Eng/doc/new_features/v184_features_doc.rst index eca801dc7..34f8a2ce3 100644 --- a/docs/source/Eng/doc/new_features/v184_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v184_features_doc.rst @@ -38,7 +38,10 @@ Headless API Focusability is role-based (the interactive roles: Button, Edit, CheckBox, ComboBox, RadioButton, Hyperlink, ListItem, MenuItem, Slider, Tab/TabItem, -TreeItem, …). ``focus_control`` locates by ``name`` / ``role`` / ``app_name`` / +TreeItem, …), read in any platform's spelling: UIA (``CheckBox``, or the raw +``ControlType_50002``), AT-SPI (``push button``), macOS AX (``AXTextField``) and +ARIA (``textbox``). A disabled control is left out of the Tab order, as ``Tab`` +skips it. ``focus_control`` locates by ``name`` / ``role`` / ``app_name`` / ``automation_id`` like the other native-control actions and returns ``bool``. Executor commands diff --git a/docs/source/Eng/doc/new_features/v192_features_doc.rst b/docs/source/Eng/doc/new_features/v192_features_doc.rst index d717e2e1f..08f555eca 100644 --- a/docs/source/Eng/doc/new_features/v192_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v192_features_doc.rst @@ -10,7 +10,9 @@ instead of mis-pairing everything — and classifies the differences: * **added** / **removed** — steps present in only one run, * **status_flips** — an aligned step whose status changed, with the new failure's :func:`failure_signature` when it carries an ``error``, -* **timing_regressions** — an aligned step that got ``regress_factor`` x slower. +* **timing_regressions** — an aligned step that got ``regress_factor`` x slower. A + step recorded at 0 s is compared as 0.1 s and reported with ``ratio`` ``None``; + non-finite or negative durations are not compared. A step is any dict with a name key (default ``"name"``) and optional ``status`` / ``duration`` / ``error``. Pure standard library; no device, no ``PySide6``. diff --git a/docs/source/Eng/doc/new_features/v193_features_doc.rst b/docs/source/Eng/doc/new_features/v193_features_doc.rst index 3abad25ec..071195b7e 100644 --- a/docs/source/Eng/doc/new_features/v193_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v193_features_doc.rst @@ -34,7 +34,8 @@ Headless API cofailure_pairs(runs, threshold=0.6) # [{"tests": ["test_a", "test_b"], "jaccard": 1.0, "co_failures": 3}] -``threshold`` is the minimum co-failure Jaccard to link two tests; ``min_size`` +``threshold`` is the minimum co-failure Jaccard to link two tests, which must also +have failed together at least once; a run given as one string is one test. ``min_size`` (default ``2``) drops singletons so only genuine clusters surface. Clusters come back largest / most cohesive first. diff --git a/docs/source/Eng/doc/new_features/v194_features_doc.rst b/docs/source/Eng/doc/new_features/v194_features_doc.rst index f8568eb60..7cc37ba02 100644 --- a/docs/source/Eng/doc/new_features/v194_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v194_features_doc.rst @@ -14,7 +14,8 @@ bottleneck steps, so you can read a single slow run instead of an average. A step is any dict with a name (default ``"name"``) and a ``duration``; an optional ``start`` places it on an absolute timeline (overlapping / parallel -steps), else steps are laid out back-to-back. Pure standard library; no device, +steps), else steps are laid out back-to-back. A duration must be finite and not +negative and a start finite, or ``ValueError`` names the step. Pure standard library; no device, no ``PySide6``. Headless API diff --git a/docs/source/Eng/doc/new_features/v209_features_doc.rst b/docs/source/Eng/doc/new_features/v209_features_doc.rst index 94f2c369c..a00538e39 100644 --- a/docs/source/Eng/doc/new_features/v209_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v209_features_doc.rst @@ -13,7 +13,8 @@ contended UI automation needs: ``retry_budget`` adds both. :class:`RetryBudget` is bounded by ``max_attempts`` *and / or* ``deadline_s``; :func:`run_with_budget` honours whichever is hit first and never sleeps past the deadline. Delays use capped exponential backoff -with a selectable jitter strategy (``full`` / ``equal`` / ``none``). The +with a selectable jitter strategy (``full`` / ``equal`` / ``none``, any case; another +value raises ``ValueError``, as does a delay that is not finite and >= 0). The randomness source (``uniform``), the clock and the sleeper are all injectable, so every delay and decision is deterministic in tests. Imports no ``PySide6``. diff --git a/docs/source/Eng/doc/new_features/v24_features_doc.rst b/docs/source/Eng/doc/new_features/v24_features_doc.rst index 1f7a95fd2..dc6826670 100644 --- a/docs/source/Eng/doc/new_features/v24_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v24_features_doc.rst @@ -47,5 +47,7 @@ Input-sequence DSL A declarative mini-language for press-hold-release chords and repeated input: action ops (``press`` / ``release`` / ``key`` / ``click`` / ``move`` / ``scroll``) plus control ops ``{op: wait, ms}`` and -``{op: repeat, times, steps:[...]}``. Returns the flattened executed log. +``{op: repeat, times, steps:[...]}``. A ``click`` without ``x`` / ``y`` clicks where +the pointer is; a ``move`` needs both. A negative ``wait`` is 0 and a non-finite +one raises ``ValueError``. Returns the flattened executed log. Exposed as ``AC_input_sequence`` / ``ac_input_sequence``. diff --git a/docs/source/Eng/doc/new_features/v2_features_doc.rst b/docs/source/Eng/doc/new_features/v2_features_doc.rst index 575fb9bb7..8a211b557 100644 --- a/docs/source/Eng/doc/new_features/v2_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v2_features_doc.rst @@ -252,7 +252,15 @@ Brand-new convenience commands on top of the existing Executor: ``AC_web_open / _quit / _screenshot / _current_url`` (joining the existing ``AC_web_run``). MCP exposes the same surface -as ``ac_web_*``. GUI: **WebRunner** tab. +as ``ac_web_*``. GUI: **WebRunner** tab; Script Builder: the +**Browser** category. + +``["AC_web_run", {"action": "WR_to_url", "params": {"url": "..."}}]`` +runs one ``WR_*`` command through WebRunner's ``execute_one`` (its +command gates, retry policy and failure screenshots) when the +installed WebRunner has it. A failing command raises +``WebRunnerBridgeError``, so the executor records it and the script +goes on, as with any other failed action. Chat-ops bot diff --git a/docs/source/Eng/doc/new_features/v35_features_doc.rst b/docs/source/Eng/doc/new_features/v35_features_doc.rst index 42557b8e4..ee9ca171a 100644 --- a/docs/source/Eng/doc/new_features/v35_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v35_features_doc.rst @@ -30,8 +30,9 @@ Headless API # .received file, then bless it: approve_artifact("invoice_render", approvals_dir="tests/.approvals") -``content`` may be ``str`` or ``bytes`` (pass ``extension="png"`` for binary -snapshots). A verified run clears any stale received file. +``content`` may be ``bytes`` (pass ``extension="png"`` for binary snapshots), +``str`` (stored as UTF-8) or any other JSON value (stored as sorted, indented +JSON). A verified run clears any stale received file. ``pending_artifacts(dir)`` lists names still awaiting approval. ``ApprovalResult`` carries ``status`` (``verified`` / ``mismatch`` / ``new``), ``match``, and both file paths. diff --git a/docs/source/Eng/doc/new_features/v36_features_doc.rst b/docs/source/Eng/doc/new_features/v36_features_doc.rst index 35b41373a..3044d0b50 100644 --- a/docs/source/Eng/doc/new_features/v36_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v36_features_doc.rst @@ -43,7 +43,9 @@ Headless API print(result["score"], result["checks"]) ``score`` is the fraction of applicable checks that passed; ``passed`` is true -only when all pass; an empty rubric trivially passes. Each entry in ``checks`` +only when all pass; an empty rubric trivially passes. A rubric that is not an +object, or that has an unknown key (``"forbiden_actions"``), raises ``ValueError`` +rather than passing with no checks. Each entry in ``checks`` is ``{name, passed, detail}`` so a failure pinpoints the violated expectation. Executor command diff --git a/docs/source/Eng/doc/new_features/v3_features_doc.rst b/docs/source/Eng/doc/new_features/v3_features_doc.rst index 465ec3751..f62b05191 100644 --- a/docs/source/Eng/doc/new_features/v3_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v3_features_doc.rst @@ -193,7 +193,9 @@ Quarantine (closing the loop) A quarantined case name is *skipped* by the suite runner (recorded as ``skipped`` with reason ``quarantined``) so a known-flaky case stops poisoning the suite's red/green status until it is fixed. The store is a -small JSON file (mode 0600 on POSIX) that persists across restarts:: +small JSON file (mode 0600 on POSIX) that persists across restarts; every change +takes the file's lock and re-reads it, so a runner, the CLI and the GUI can share +one file without overwriting each other:: from je_auto_control import ( default_quarantine_store, auto_quarantine_from_flakiness, diff --git a/docs/source/Eng/doc/new_features/v47_features_doc.rst b/docs/source/Eng/doc/new_features/v47_features_doc.rst index 5acddfde9..31c180f6e 100644 --- a/docs/source/Eng/doc/new_features/v47_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v47_features_doc.rst @@ -35,6 +35,7 @@ Executor command ---------------- ``AC_mine_actions`` takes ``actions`` (a list, or a JSON-string list from the -visual builder) plus ``min_len`` / ``max_len`` / ``min_count`` and returns +visual builder) plus ``min_len`` / ``max_len`` / ``min_count`` (``min_len`` and +``min_count`` at least 1, ``max_len`` at least ``min_len``, else ``ValueError``) and returns ``{total_actions, patterns, candidates}``. The same operation is exposed as the MCP tool ``ac_mine_actions`` and as a Script Builder command under **Report**. diff --git a/docs/source/Eng/doc/new_features/v48_features_doc.rst b/docs/source/Eng/doc/new_features/v48_features_doc.rst index 41456c233..1c1225925 100644 --- a/docs/source/Eng/doc/new_features/v48_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v48_features_doc.rst @@ -36,7 +36,9 @@ Types are ``text`` / ``int`` / ``bool`` / ``credential``; ``get`` coerces to the declared type and falls back to the ``default`` environment unless disabled. ``active_environment()`` reads ``JE_AUTOCONTROL_ENV``. ``list`` / ``delete`` round out the store. ``set`` refuses an unknown type and a value the type cannot read -(``"eighty"`` as an ``int``) at write time. +(``"eighty"`` as an ``int``) at write time. An ``int`` takes whole numbers only (not +``3.7`` or ``True``), and a ``bool`` string must be one of true / false / yes / no / +on / off / 1 / 0. Executor commands ----------------- diff --git a/docs/source/Eng/doc/new_features/v4_features_doc.rst b/docs/source/Eng/doc/new_features/v4_features_doc.rst index c1b868985..378ba0571 100644 --- a/docs/source/Eng/doc/new_features/v4_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v4_features_doc.rst @@ -74,7 +74,8 @@ Flow control & variables assertion DSL. * **Read into a variable** — bind external data into the flow scope for later ``${var}`` use: ``AC_ocr_to_var`` (region text), ``AC_shell_to_var`` - (command stdout, decoded with ``encoding`` -- default the locale's; a + (command stdout, decoded with ``encoding`` -- default the locale's code + page, also under Python 3.15's UTF-8 mode; a timeout ends the command and everything it started, and a ``.bat`` / ``.cmd`` argument holding cmd syntax is refused), ``AC_read_file_to_var`` (file text; UTF-8 with or without a byte-order mark unless ``encoding`` diff --git a/docs/source/Eng/doc/new_features/v51_features_doc.rst b/docs/source/Eng/doc/new_features/v51_features_doc.rst index cf966623f..52c975774 100644 --- a/docs/source/Eng/doc/new_features/v51_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v51_features_doc.rst @@ -23,7 +23,11 @@ compared value is a JSON number, a quoted string, ``true``, ``false`` or ``null``. Values of different types never compare equal (``true != 1``), and ``<`` / ``>`` order only two numbers or two strings (``<=`` is ``<`` or ``==``, so ``null <= null``). Quoted names and strings decode RFC 9535 -escapes (``['a\'b']`` is the key ``a'b``). +escapes (``['a\'b']`` is the key ``a'b``, and ``["\uD83D\uDE00"]`` the +one-character key it spells); an unpaired surrogate, a raw control character +or the other kind of quote escaped raises. An index is an RFC 9535 integer: +``[01]``, ``[-0]`` and indices past 2**53-1 raise, and ``..`` must be followed +by a name, ``*`` or ``[...]``. A path the subset cannot read -- an unsupported filter or value, a slice (``[0:2]``) or union (``[0,1]``, ``['a','b']``), an empty ``[]``, an unterminated ``[``, a stray character -- raises ``ValueError`` instead of matching something else. diff --git a/docs/source/Eng/doc/new_features/v54_features_doc.rst b/docs/source/Eng/doc/new_features/v54_features_doc.rst index 74d56fdde..c92e1b986 100644 --- a/docs/source/Eng/doc/new_features/v54_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v54_features_doc.rst @@ -51,5 +51,7 @@ Command Effect ``AC_repair_approve`` Approve a pending suggestion. ================================ =================================================== -The same operations are exposed as MCP tools (``ac_repair_*``) and as Script -Builder commands under **Tools**. +Each takes an optional ``db`` (a JSON path); without one, the commands share one +in-memory store per process (``repair_store()``), so a suggestion recorded by +``AC_repair_record`` is found by the others. The same operations are exposed as MCP +tools (``ac_repair_*``) and as Script Builder commands under **Tools**. diff --git a/docs/source/Eng/doc/new_features/v57_features_doc.rst b/docs/source/Eng/doc/new_features/v57_features_doc.rst index d946b8672..b26d1183d 100644 --- a/docs/source/Eng/doc/new_features/v57_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v57_features_doc.rst @@ -21,7 +21,16 @@ Supported keywords depth). * numbers — ``minimum`` / ``maximum`` / ``exclusiveMinimum`` / ``exclusiveMaximum`` / ``multipleOf``. -* strings — ``minLength`` / ``maxLength`` / ``pattern``. +* strings — ``minLength`` / ``maxLength`` / ``pattern``. ``pattern`` and + ``patternProperties`` match as ECMA-262 does, which is what the + specification says: ``$`` does not match before a trailing newline; + ``\d``, ``\w`` and ``\b`` are ASCII, so ``^\d+$`` rejects Arabic-Indic + digits; ``.`` stops at every line terminator; ``\s`` is ECMA-262 + whitespace. ``\cX``, ``\u{...}``, ``[]`` / ``[^]``, ``(?...)`` / + ``\k`` and ``\p{...}`` / ``\P{...}`` for the General_Category + values (``L``, ``Letter``, ``Nd``, ``digit``, ``gc=Lu``...) plus ``Any``, + ``ASCII`` and ``Assigned`` are understood; script properties raise + ``AutoControlJsonException``. * arrays — ``minItems`` / ``maxItems`` / ``uniqueItems`` / ``items`` / ``prefixItems`` / ``contains``. * objects — ``required`` / ``minProperties`` / ``maxProperties`` / diff --git a/docs/source/Eng/doc/new_features/v61_features_doc.rst b/docs/source/Eng/doc/new_features/v61_features_doc.rst index 7b9d2d31c..2e6e03764 100644 --- a/docs/source/Eng/doc/new_features/v61_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v61_features_doc.rst @@ -34,10 +34,10 @@ Headless API token = encode_jwt({"sub": "user1", "aud": "api", "exp": 1893456000}, secret) # -> "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...." - # default policy: HS256 only, verify exp/nbf, no audience/issuer check - claims = decode_jwt(token, secret) + # the token carries "aud", so the policy must name the audience to accept + claims = decode_jwt(token, secret, ClaimsPolicy(audience="api")) - # tighten the policy for audience / issuer / leeway / algorithms + # tighten the policy for issuer / leeway / algorithms as well policy = ClaimsPolicy(algorithms=("HS256",), audience="api", issuer="my-service", leeway=30) claims = decode_jwt(token, secret, policy) @@ -47,7 +47,11 @@ Headless API validates the standard claims against a :class:`ClaimsPolicy` (``exp`` / ``nbf`` with ``leeway``, ``aud`` membership, ``iss`` match) using an injectable ``now``; it raises ``ExpiredTokenError`` / ``InvalidSignatureError`` / ``JwtError`` on -failure. The minted token drops straight into the HTTP client: +failure. The default policy is HS256 only with ``exp`` / ``nbf`` checked; a +token that carries ``aud`` is refused unless ``ClaimsPolicy.audience`` names +one of its values (RFC 7519 4.1.3), so a token minted for one service does not +verify at another that shares the key. An empty or non-string key, and claims +or headers that are not JSON (a ``datetime``, ``NaN``), raise ``JwtError``. The minted token drops straight into the HTTP client: .. code-block:: python diff --git a/docs/source/Eng/doc/new_features/v62_features_doc.rst b/docs/source/Eng/doc/new_features/v62_features_doc.rst index a112bc005..bb59cb863 100644 --- a/docs/source/Eng/doc/new_features/v62_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v62_features_doc.rst @@ -48,6 +48,7 @@ Executor command ``AC_rate_limit`` takes a limiter ``name`` plus ``rate`` / ``capacity`` / ``n`` and tries to take ``n`` tokens from that named token bucket (created on first -use), returning ``{acquired, tokens, wait}`` so a flow can gate or defer an +use, and rebuilt when a call names a different ``rate`` or ``capacity``; the +executor and MCP share one set of buckets, ``named_bucket``), returning ``{acquired, tokens, wait}`` so a flow can gate or defer an action. The same operation is exposed as the MCP tool ``ac_rate_limit`` and as a Script Builder command under **Flow**. diff --git a/docs/source/Eng/doc/new_features/v64_features_doc.rst b/docs/source/Eng/doc/new_features/v64_features_doc.rst index ae004e9c6..64af30996 100644 --- a/docs/source/Eng/doc/new_features/v64_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v64_features_doc.rst @@ -34,12 +34,15 @@ Headless API ``SearchIndex.add`` / ``remove`` keep the index up to date incrementally; ``build`` indexes a ``{doc_id: text}`` map (or ``(id, text)`` pairs). ``search`` returns ranked ``SearchHit(doc_id, score)`` results — by default BM25 -(``k1=1.5``, ``b=0.75``), or ``mode="tfidf"``. The scoring is the standard +(``k1=1.5``, ``b=0.75``), or ``mode="tfidf"`` (log-scaled term frequency +times ``ln(N / df) + 1``, so a term in every document still counts). The BM25 scoring is the standard Okapi formula with ``IDF = ln(1 + (N − df + 0.5) / (df + 0.5))``, so a rare term out-ranks a common one, term-frequency saturates (``k1``), and long documents are normalized down (``b``). A ``stop_words`` set can be supplied to drop noise terms. Terms are case-folded runs of letters and digits in any script, so -``登入`` and ``café`` are indexed whole; stop words are folded the same way. Results are deterministic (ties broken by ``doc_id``). +``café`` is indexed whole; a run of kana, CJK ideographs or Hangul, written +without spaces, is indexed as its character bigrams, so ``登入`` is found inside +``請先登入系統``. Stop words are folded the same way. Results are deterministic (ties broken by ``doc_id``). Executor command ---------------- diff --git a/docs/source/Eng/doc/new_features/v66_features_doc.rst b/docs/source/Eng/doc/new_features/v66_features_doc.rst index 598a7d4e0..7097b858c 100644 --- a/docs/source/Eng/doc/new_features/v66_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v66_features_doc.rst @@ -10,7 +10,10 @@ Supported rule parts: ``FREQ`` (DAILY/WEEKLY/MONTHLY/YEARLY), ``INTERVAL``, ``COUNT``, ``UNTIL``, ``BYDAY`` (incl. ordinals like ``2MO`` / ``-1FR``), ``BYMONTHDAY`` (incl. negatives), ``BYMONTH``, ``BYSETPOS`` and ``WKST``. Time-level parts and BYWEEKNO/BYYEARDAY are out of scope: ``parse_rrule`` raises -``AutoControlException`` for them, and for a rule with both ``COUNT`` and ``UNTIL``. Pure standard library +``AutoControlException`` for them, for a rule with both ``COUNT`` and ``UNTIL``, +and for combinations RFC 5545 forbids (``BYMONTHDAY`` with ``WEEKLY``, a numbered +``BYDAY`` with ``DAILY`` / ``WEEKLY``, an unknown ``WKST``). Without ``BYMONTH`` +a ``YEARLY`` ``BYDAY`` ordinal counts within the year. Pure standard library (``datetime`` + ``calendar``); the clock is injectable so ``next_occurrence`` is deterministic. Imports no ``PySide6``. @@ -37,8 +40,11 @@ Headless API ``parse_rrule`` accepts the rule with or without the ``RRULE:`` prefix and returns a frozen ``Recurrence``. ``occurrences`` yields datetimes anchored at ``dtstart`` (its time-of-day and timezone are applied to every occurrence), -bounded by ``COUNT`` / ``UNTIL`` (or the ``count=`` / ``until=`` overrides) and -a safety cap. A date-only ``UNTIL`` bounds the whole day inclusively. +bounded by ``COUNT`` / ``UNTIL``; the ``count=`` / ``until=`` arguments narrow +them further (the smaller wins) and ``max_iter`` caps a rule without a count. +A series ends at 9999-12-31, or once 400 years (scaled up for longer +intervals) pass without an occurrence, so a rule that can never match still +ends. A date-only ``UNTIL`` bounds the whole day inclusively. ``next_occurrence`` returns the first occurrence at or after ``now``. Executor commands diff --git a/docs/source/Eng/doc/new_features/v76_features_doc.rst b/docs/source/Eng/doc/new_features/v76_features_doc.rst index c4c5d7f52..ac6a3fdd2 100644 --- a/docs/source/Eng/doc/new_features/v76_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v76_features_doc.rst @@ -32,11 +32,19 @@ Headless API ``SpanContext`` is the immutable (``trace_id``, ``span_id``, ``trace_flags``, ``tracestate``) tuple. ``new_root_context`` mints a fresh trace; ``child_context`` -keeps the trace id and inherited state but allocates a new span id. +keeps the trace id and inherited state but allocates a new span id, clearing +flag bits other than sampled and random. ``parse_traceparent`` / ``format_traceparent`` round-trip the version-``00`` header (a newer version is read as ``00`` with any extra fields ignored; version -``ff``, malformed or all-zero IDs raise ``TraceContextError``); ``parse_tracestate`` / ``format_tracestate`` handle the -vendor list. ``inject_context`` writes the headers; ``extract_context`` reads +``ff``, malformed or all-zero IDs raise ``TraceContextError``, and +``format_traceparent`` validates a hand-built context before writing it); +``parse_tracestate`` / ``format_tracestate`` handle the vendor list. Parsing +trims only the spaces and tabs around commas, keeps a value's leading spaces, +discards a member whose key or value breaks the grammar (an empty value, ``=`` +or ``,`` inside, over 256 characters, a control character), stops after 32 +members, and returns ``[]`` for a duplicated key; formatting raises +``TraceContextError`` for such a member instead of writing it out. +``inject_context`` writes the headers; ``extract_context`` reads them back (case-insensitively) and returns ``None`` for a missing or invalid ``traceparent``, so the receiver starts a new trace as W3C Trace Context says. diff --git a/docs/source/Eng/doc/new_features/v77_features_doc.rst b/docs/source/Eng/doc/new_features/v77_features_doc.rst index 5cf6ff9ca..a42fc0791 100644 --- a/docs/source/Eng/doc/new_features/v77_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v77_features_doc.rst @@ -28,11 +28,14 @@ Headless API ``profile_rows`` returns ``{row_count, columns}`` where each column carries its count, null count and fraction, distinct count, a uniqueness flag, the inferred -type (``int`` / ``number`` / ``bool`` / ``str``), the top values with counts, -and ``min`` / ``max`` / ``mean`` for numeric columns. ``infer_schema`` turns +type (``int`` / ``number`` / ``bool`` / ``str``, or ``mixed`` when no one type +holds), the top values with counts (``True`` and ``1`` count apart), and +``min`` / ``max`` / ``mean`` for numeric columns (exact for ``int`` columns, +over the finite values otherwise). ``infer_schema`` turns that profile into a schema the existing ``validate_rows`` understands: a column is ``required`` when it has no nulls, ``unique`` when every non-null value is -distinct, and carries numeric bounds. Pass an explicit ``columns`` list to +distinct, and carries numeric bounds; a ``mixed`` column gets no ``type`` rule. +Pass an explicit ``columns`` list to restrict either function to a subset. Executor commands diff --git a/docs/source/Eng/doc/new_features/v78_features_doc.rst b/docs/source/Eng/doc/new_features/v78_features_doc.rst index 3fcacf598..1c00c5ed2 100644 --- a/docs/source/Eng/doc/new_features/v78_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v78_features_doc.rst @@ -26,7 +26,8 @@ Headless API # or convert a problem response into an exception: raise_for_problem(response) # raises HttpProblemError -``is_problem`` checks the ``Content-Type`` (case-insensitively). +``is_problem`` checks that the ``Content-Type`` media type is +``application/problem+json`` (case-insensitively, parameters ignored). ``parse_problem`` returns a ``ProblemDetails`` (``type`` defaulting to ``about:blank``, an integer ``status`` when coercible, and all non-registered keys collected into ``extensions``) or ``None`` when the response is not a diff --git a/docs/source/Eng/doc/new_features/v79_features_doc.rst b/docs/source/Eng/doc/new_features/v79_features_doc.rst index 395470e3a..fc8f8cf5a 100644 --- a/docs/source/Eng/doc/new_features/v79_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v79_features_doc.rst @@ -28,11 +28,16 @@ Headless API ``export`` prefix, validates keys, and resolves values: single-quoted values are literal apart from ``\'`` and ``\\`` (as python-dotenv reads them), double-quoted values process ``\n`` / ``\t`` / ``\\`` / ``\"`` escapes, and unquoted values drop a trailing `` #`` comment and surrounding -whitespace. A quoted value ends at its closing quote, so a comment after it is -dropped, and it may span several lines. ``dotenv_values`` reads and parses a file; ``load_dotenv`` merges a +whitespace; ``#`` starts a comment only after whitespace, so ``COLOR=#ff0000`` +keeps its value while ``KEY= # note`` is empty. A quoted value ends at its +closing quote, so a comment after it is dropped, and it may span several lines, +keeping each line's trailing whitespace. A leading byte-order mark is skipped. +``dotenv_values`` reads and parses a file; ``load_dotenv`` merges a file into an explicit ``env`` mapping (keeping existing keys unless ``override``); ``dump_dotenv`` serialises a mapping back to ``.env`` text, -quoting values that need it. +quoting values that need it, and raises ``DotenvError`` (an +``AutoControlException`` and a ``ValueError``) for a key the parser would not +read back, such as one holding a line break or ``=``. Executor commands ----------------- diff --git a/docs/source/Eng/doc/new_features/v81_features_doc.rst b/docs/source/Eng/doc/new_features/v81_features_doc.rst index e5b820e65..92dcd9f9b 100644 --- a/docs/source/Eng/doc/new_features/v81_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v81_features_doc.rst @@ -34,7 +34,8 @@ defaults to insertion order, so later layers override earlier ones). merged recursively while scalars and lists are replaced. ``get`` reads a dotted key from the resolved config with a default; ``explain`` returns a ``SourceTrace`` naming the winning layer for a dotted key (raising ``KeyError`` -when absent). ``deep_merge`` is exposed as a standalone two-mapping helper. +when absent). ``deep_merge`` is exposed as a standalone two-mapping helper; its +result shares no nested value with either input. Executor commands ----------------- diff --git a/docs/source/Eng/doc/new_features/v82_features_doc.rst b/docs/source/Eng/doc/new_features/v82_features_doc.rst index e74a88c38..b11e2596c 100644 --- a/docs/source/Eng/doc/new_features/v82_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v82_features_doc.rst @@ -30,7 +30,8 @@ diverge). ``ks_two_sample`` returns the maximum empirical-CDF gap and a p-value from the Kolmogorov distribution. ``categorical_drift`` compares label frequencies via a chi-square statistic and the total-variation distance. ``detect_drift`` wraps the numeric path into one report with a ``drifted`` -verdict at ``threshold`` (default ``0.25``). +verdict at ``threshold`` (default ``0.25``). A NaN in either numeric sample raises +``ValueError`` naming its index: drop or impute missing values first. Executor commands ----------------- diff --git a/docs/source/Eng/doc/new_features/v83_features_doc.rst b/docs/source/Eng/doc/new_features/v83_features_doc.rst index 3240a0ae4..75eb48d52 100644 --- a/docs/source/Eng/doc/new_features/v83_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v83_features_doc.rst @@ -29,7 +29,8 @@ Headless API ``diff_rows`` keys both row-sets and returns ``{added, removed, changed, unchanged}``: ``added`` / ``removed`` / ``unchanged`` are row lists, while ``changed`` holds ``{key, old, new}`` entries (the key is a scalar for a single -column or a list for a composite key). On duplicate keys the last row wins. +column or a list for a composite key). On duplicate keys the last row wins; an empty +key, or a row without a key column, raises ``ValueError``. NaN cells equal NaN. ``cell_changes`` expands the changed rows into ``{key, column, old, new}`` records. ``summarize_diff`` counts each bucket. diff --git a/docs/source/Eng/doc/new_features/v84_features_doc.rst b/docs/source/Eng/doc/new_features/v84_features_doc.rst index 1a93fb172..d0c0dfa90 100644 --- a/docs/source/Eng/doc/new_features/v84_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v84_features_doc.rst @@ -27,7 +27,8 @@ Headless API ``Baggage`` wraps an immutable key-value map: ``get`` reads, ``set`` / ``remove`` return new instances, and ``to_dict`` exports the entries. ``parse_baggage`` reads the header (dropping optional ``;metadata`` and rejecting empty keys), -``format_baggage`` percent-encodes keys and values back into a header value, +``format_baggage`` percent-encodes keys and values back into a header value +(``Baggage`` refuses an empty key with ``ValueError``, since no parser reads it back), and ``inject_baggage`` / ``extract_baggage`` write and read the ``baggage`` header on a request dict (extraction is case-insensitive). Pairs naturally with ``trace_context`` to carry context alongside the trace. diff --git a/docs/source/Eng/doc/new_features/v85_features_doc.rst b/docs/source/Eng/doc/new_features/v85_features_doc.rst index 5eab9dcf5..fbedfae32 100644 --- a/docs/source/Eng/doc/new_features/v85_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v85_features_doc.rst @@ -43,3 +43,7 @@ Executor commands ``AC_resolve_refs`` resolves every reference inside ``obj`` and returns ``{resolved}``. Both are exposed as MCP tools (``ac_resolve_ref`` / ``ac_resolve_refs``) and as Script Builder commands under **Security**. +Their results are recorded (the executor record, the MCP result), so they +refuse ``secret://`` with ``SecretRefError``: secret values never enter those +records. Reference ``${secrets.NAME}`` in the step that needs the value, or call +``resolve_ref`` from Python. diff --git a/docs/source/Eng/doc/new_features/v87_features_doc.rst b/docs/source/Eng/doc/new_features/v87_features_doc.rst index ad0557300..a1dcfdbdb 100644 --- a/docs/source/Eng/doc/new_features/v87_features_doc.rst +++ b/docs/source/Eng/doc/new_features/v87_features_doc.rst @@ -26,8 +26,12 @@ Headless API pages = paginate(start_url, fetch, max_pages=50) ``parse_link_header`` returns a list of ``Link`` (``uri``, ``rel``, and all -``params``), tolerating quoted values that contain commas and multiple links in -one header. ``links_by_rel`` indexes by each (space-separated) relation, +``params``), reading the header with the RFC 8288 Appendix B algorithm: quoted +values keep their commas, semicolons and escapes, an unquoted value runs to the +next ``;`` or ``,`` (so ``title=x= 0 的 +延遲也一樣)。隨機來源(``uniform``)、時鐘與睡眠器 皆可注入,故每個延遲與決策在測試中都是確定的。不匯入 ``PySide6``。 無頭 API diff --git a/docs/source/Zh/doc/new_features/v24_features_doc.rst b/docs/source/Zh/doc/new_features/v24_features_doc.rst index fb865bf1f..f5beddde0 100644 --- a/docs/source/Zh/doc/new_features/v24_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v24_features_doc.rst @@ -45,5 +45,6 @@ 用於按住-放開組合鍵與重複輸入的宣告式迷你語言:動作 op(``press`` / ``release`` / ``key`` / ``click`` / ``move`` / ``scroll``)加上控制 op -``{op: wait, ms}`` 與 ``{op: repeat, times, steps:[...]}``。回傳攤平後的 +``{op: wait, ms}`` 與 ``{op: repeat, times, steps:[...]}``。沒有 ``x`` / ``y`` 的 ``click`` 在指標所在處點擊; +``move`` 兩者都要有。負的 ``wait`` 視為 0,非有限值丟出 ``ValueError``。回傳攤平後的 執行記錄。對應 ``AC_input_sequence`` / ``ac_input_sequence``。 diff --git a/docs/source/Zh/doc/new_features/v2_features_doc.rst b/docs/source/Zh/doc/new_features/v2_features_doc.rst index 4edae758d..81e5045ed 100644 --- a/docs/source/Zh/doc/new_features/v2_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v2_features_doc.rst @@ -233,7 +233,13 @@ WebRunner 接入 executor + MCP Executor:``AC_web_open / _quit / _screenshot / _current_url`` (加上既有的 ``AC_web_run``)。MCP 同步以 ``ac_web_*`` 暴露。 -GUI:**WebRunner** 分頁。 +GUI:**WebRunner** 分頁;Script Builder:**Browser** 分類。 + +``["AC_web_run", {"action": "WR_to_url", "params": {"url": "..."}}]`` +在安裝的 WebRunner 有 ``execute_one`` 時,經由它執行單一 ``WR_*`` 命令 +(WebRunner 的命令閘門、重試策略與失敗截圖)。命令失敗時拋出 +``WebRunnerBridgeError``,executor 會像其他失敗的動作一樣記下它, +腳本繼續執行。 Chat-ops 機器人 diff --git a/docs/source/Zh/doc/new_features/v35_features_doc.rst b/docs/source/Zh/doc/new_features/v35_features_doc.rst index 348cfd67d..3b139f5f1 100644 --- a/docs/source/Zh/doc/new_features/v35_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v35_features_doc.rst @@ -26,7 +26,8 @@ # 首次執行為 "new",輸出變更為 "mismatch";審查 .received 檔後再核可: approve_artifact("invoice_render", approvals_dir="tests/.approvals") -``content`` 可為 ``str`` 或 ``bytes``(二進位快照請傳 ``extension="png"``)。相符的執 +``content`` 可為 ``bytes``(二進位快照請傳 ``extension="png"``)、``str``(以 UTF-8 儲存)或其他任何 JSON 值 +(以排序、縮排的 JSON 儲存)。相符的執 行會清除任何過期的 received 檔。``pending_artifacts(dir)`` 列出仍待核准的名稱。 ``ApprovalResult`` 帶有 ``status``(``verified`` / ``mismatch`` / ``new``)、 ``match`` 及兩個檔案路徑。 diff --git a/docs/source/Zh/doc/new_features/v36_features_doc.rst b/docs/source/Zh/doc/new_features/v36_features_doc.rst index 0fdf7ffbc..5df6802a3 100644 --- a/docs/source/Zh/doc/new_features/v36_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v36_features_doc.rst @@ -40,7 +40,8 @@ Rubric 鍵 意義 assert result["passed"] # 所有適用的檢查都通過 print(result["score"], result["checks"]) -``score`` 為通過的適用檢查佔比;``passed`` 僅在全部通過時為真;空 rubric 直接通過。 +``score`` 為通過的適用檢查佔比;``passed`` 僅在全部通過時為真;空 rubric 直接通過。rubric 不是物件,或含有未知的鍵(``"forbiden_actions"``) +時丟出 ``ValueError``,而不是在沒有任何檢查的情況下通過。 ``checks`` 中每個項目為 ``{name, passed, detail}``,因此失敗時可精準指出被違反的期望。 執行器指令 diff --git a/docs/source/Zh/doc/new_features/v3_features_doc.rst b/docs/source/Zh/doc/new_features/v3_features_doc.rst index c506fe255..994515499 100644 --- a/docs/source/Zh/doc/new_features/v3_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v3_features_doc.rst @@ -174,7 +174,8 @@ GUI:**Flaky Tests** 分頁。 被隔離的案例名稱會被套件執行器*跳過*(記為 ``skipped``,原因 ``quarantined``), 讓已知不穩定的案例在修好前不再污染套件的紅 / 綠狀態。隔離區是一個小型 JSON -檔(POSIX 上為 0600 權限),可跨重啟保存:: +檔(POSIX 上為 0600 權限),可跨重啟保存;每次變更都會取得檔案鎖並重新讀取,所以執行器、CLI 與 GUI 可以共用同一個 +檔案而不互相覆蓋:: from je_auto_control import ( default_quarantine_store, auto_quarantine_from_flakiness, diff --git a/docs/source/Zh/doc/new_features/v47_features_doc.rst b/docs/source/Zh/doc/new_features/v47_features_doc.rst index e3ee9b3a5..195f22c55 100644 --- a/docs/source/Zh/doc/new_features/v47_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v47_features_doc.rst @@ -31,6 +31,7 @@ AutoControl 一直錄製豐富的動作日誌卻從未分析;``mine_action_log`` ---------- ``AC_mine_actions`` 接受 ``actions``(清單,或視覺化建構器傳入的 JSON 字串清單)以及 -``min_len`` / ``max_len`` / ``min_count``,並回傳 ``{total_actions, patterns, +``min_len`` / ``max_len`` / ``min_count``(``min_len`` 與 ``min_count`` 至少為 1,``max_len`` 不小於 +``min_len``,否則丟出 ``ValueError``),並回傳 ``{total_actions, patterns, candidates}``。相同操作亦提供為 MCP 工具 ``ac_mine_actions``,以及 Script Builder 中 **Report** 分類下的指令。 diff --git a/docs/source/Zh/doc/new_features/v48_features_doc.rst b/docs/source/Zh/doc/new_features/v48_features_doc.rst index 2f7720d24..85b5bec6f 100644 --- a/docs/source/Zh/doc/new_features/v48_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v48_features_doc.rst @@ -32,7 +32,8 @@ JSON 後端(或記憶體內);純標準函式庫;不匯入 ``PySide6``。 型別為 ``text`` / ``int`` / ``bool`` / ``credential``;``get`` 會轉成宣告型別,並在未停用 時退回 ``default`` 環境。``active_environment()`` 讀取 ``JE_AUTOCONTROL_ENV``。``list`` / ``delete`` 補齊整個儲存體。``set`` 在寫入時就拒絕未知型別,以及該型別讀不懂的值(例如把 -``"eighty"`` 當 ``int``)。 +``"eighty"`` 當 ``int``)。``int`` 只接受整數(不接受 ``3.7`` 或 ``True``),``bool`` 字串必須是 true / false / yes / +no / on / off / 1 / 0 之一。 執行器指令 ---------- diff --git a/docs/source/Zh/doc/new_features/v4_features_doc.rst b/docs/source/Zh/doc/new_features/v4_features_doc.rst index 98e89d2df..2a010e192 100644 --- a/docs/source/Zh/doc/new_features/v4_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v4_features_doc.rst @@ -64,7 +64,7 @@ Builder 項目。視覺與視窗功能的 geometry / IO 操作皆可注入,因 ``AC_assert_duration`` 在區塊耗時超過預算時判失敗——銜接 profiler 與 斷言 DSL 的延遲回歸守門。 * **讀進變數** — 把外部資料綁進流程範圍供後續 ``${var}`` 使用: - ``AC_ocr_to_var``(區域文字)、``AC_shell_to_var``(命令 stdout,以 ``encoding`` 解碼,預設為系統地區設定的編碼; + ``AC_ocr_to_var``(區域文字)、``AC_shell_to_var``(命令 stdout,以 ``encoding`` 解碼,預設為系統地區設定的字碼頁,Python 3.15 的 UTF-8 模式下也是; 逾時會結束該命令及它啟動的所有程序,``.bat`` / ``.cmd`` 的參數含 cmd 語法時會拒絕)、 ``AC_read_file_to_var``(檔案文字;除非以 ``encoding`` 指定,否則讀 UTF-8,有無 BOM 皆可)、``AC_http_to_var``(GET body 或 dotted JSON path)、``AC_now_to_var``(strftime)、``AC_random_to_var`` diff --git a/docs/source/Zh/doc/new_features/v51_features_doc.rst b/docs/source/Zh/doc/new_features/v51_features_doc.rst index 709aea964..caed69521 100644 --- a/docs/source/Zh/doc/new_features/v51_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v51_features_doc.rst @@ -18,7 +18,9 @@ JSONPath 查詢 過濾條件的欄位可以是巢狀的(``@.a.b``),``[?(@.k)]`` 保留有 ``k`` 的元素;對物件套用過濾時,挑的是它的成員值。 比較的值是 JSON 數字、加引號的字串、``true``、``false`` 或 ``null``。 不同型別的值一律不相等(``true != 1``);``<`` / ``>`` 只比較兩個數字或兩個字串(``<=`` 是 ``<`` 或 ``==``, -所以 ``null <= null`` 成立)。加引號的名稱和字串會解碼 RFC 9535 的跳脫序列(``['a\'b']`` 是鍵 ``a'b``)。 +所以 ``null <= null`` 成立)。加引號的名稱和字串會解碼 RFC 9535 的跳脫序列(``['a\'b']`` 是鍵 ``a'b``, +``["\uD83D\uDE00"]`` 是它拼出的那一個字元的鍵);落單的代理字元、未跳脫的控制字元,或跳脫了另一種引號,都會拋錯。 +索引是 RFC 9535 的整數:``[01]``、``[-0]`` 與超過 2**53-1 的索引會拋錯,``..`` 後面必須接名稱、``*`` 或 ``[...]``。 這個子集讀不懂的路徑(不支援的過濾條件或值、切片 ``[0:2]``、 聯集 ``[0,1]``、``['a','b']``、空的 ``[]``、沒有收尾的 ``[``、多餘的字元)會拋 ``ValueError``,不會改成比對到別的東西。 diff --git a/docs/source/Zh/doc/new_features/v54_features_doc.rst b/docs/source/Zh/doc/new_features/v54_features_doc.rst index 54e79fe0f..1c4fc1fdc 100644 --- a/docs/source/Zh/doc/new_features/v54_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v54_features_doc.rst @@ -47,5 +47,6 @@ JSON 後端(透過共用 ``json_store`` 助手);純標準函式庫;信心與門 ``AC_repair_approve`` 核准一個待審建議。 ================================ =================================================== -相同操作亦提供為 MCP 工具(``ac_repair_*``),以及 Script Builder 中 **Tools** 分類下的指 -令。 +每個指令都可選填 ``db``(JSON 路徑);沒給時,同一個行程內的指令共用一個記憶體中的儲存區(``repair_store()``), +所以 ``AC_repair_record`` 記下的建議其他指令找得到。相同操作亦提供為 MCP 工具(``ac_repair_*``),以及 Script Builder 中 +**Tools** 分類下的指令。 diff --git a/docs/source/Zh/doc/new_features/v57_features_doc.rst b/docs/source/Zh/doc/new_features/v57_features_doc.rst index 3802ae388..d04445726 100644 --- a/docs/source/Zh/doc/new_features/v57_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v57_features_doc.rst @@ -18,7 +18,13 @@ JSON Schema 驗證 ``enum`` / ``const``(讓 ``True`` 與 ``1`` 保持相異,巢狀內也一樣)。 * 數字 —— ``minimum`` / ``maximum`` / ``exclusiveMinimum`` / ``exclusiveMaximum`` / ``multipleOf``。 -* 字串 —— ``minLength`` / ``maxLength`` / ``pattern``。 +* 字串 —— ``minLength`` / ``maxLength`` / ``pattern``。``pattern`` 與 + ``patternProperties`` 照規格用 ECMA-262 的語意比對:``$`` 不會在結尾換行之前成立; + ``\d``、``\w``、``\b`` 只認 ASCII,所以 ``^\d+$`` 不接受阿拉伯-印度數字;``.`` 遇到 + 任何行終止字元都停;``\s`` 是 ECMA-262 的空白。``\cX``、``\u{...}``、``[]`` / ``[^]``、 + ``(?...)`` / ``\k``,以及 General_Category 值(``L``、``Letter``、``Nd``、 + ``digit``、``gc=Lu``……)與 ``Any``、``ASCII``、``Assigned`` 的 ``\p{...}`` / + ``\P{...}`` 都看得懂;文字系統(script)屬性會丟出 ``AutoControlJsonException``。 * 陣列 —— ``minItems`` / ``maxItems`` / ``uniqueItems`` / ``items`` / ``prefixItems`` / ``contains``。 * 物件 —— ``required`` / ``minProperties`` / ``maxProperties`` / ``properties`` / diff --git a/docs/source/Zh/doc/new_features/v61_features_doc.rst b/docs/source/Zh/doc/new_features/v61_features_doc.rst index ba2ff05df..a9d1ee3d4 100644 --- a/docs/source/Zh/doc/new_features/v61_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v61_features_doc.rst @@ -29,10 +29,10 @@ bearer JWT。本功能補上一個聚焦、純標準函式庫的 JWT 編解碼 token = encode_jwt({"sub": "user1", "aud": "api", "exp": 1893456000}, secret) # -> "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...." - # 預設政策:僅 HS256、驗證 exp/nbf、不檢查 audience/issuer - claims = decode_jwt(token, secret) + # token 帶有 "aud",所以政策必須指名要接受的 audience + claims = decode_jwt(token, secret, ClaimsPolicy(audience="api")) - # 以 ClaimsPolicy 收緊 audience / issuer / leeway / algorithms + # 再以 ClaimsPolicy 收緊 issuer / leeway / algorithms policy = ClaimsPolicy(algorithms=("HS256",), audience="api", issuer="my-service", leeway=30) claims = decode_jwt(token, secret, policy) @@ -40,7 +40,10 @@ bearer JWT。本功能補上一個聚焦、純標準函式庫的 JWT 編解碼 ``encode_jwt`` 以 ``HS256`` / ``HS384`` / ``HS512`` 簽出精簡的 ``header.payload.signature`` token。``decode_jwt`` 先驗證簽章,再以一份 :class:`ClaimsPolicy` (含 ``leeway`` 的 ``exp`` / ``nbf``、``aud`` 成員資格、``iss`` 比對)使用可注入的 ``now`` 驗證 -標準宣告;失敗時拋出 ``ExpiredTokenError`` / ``InvalidSignatureError`` / ``JwtError``。簽出的 +標準宣告;失敗時拋出 ``ExpiredTokenError`` / ``InvalidSignatureError`` / ``JwtError``。預設政策只接受 +HS256 並檢查 ``exp`` / ``nbf``;帶有 ``aud`` 的 token,除非 ``ClaimsPolicy.audience`` 指名其中一個值,否則一律拒絕 +(RFC 7519 4.1.3),所以發給某個服務的 token 不會在共用同一把金鑰的另一個服務上驗證通過。空的或非字串的金鑰, +以及不是 JSON 的宣告或標頭(``datetime``、``NaN``),都會拋出 ``JwtError``。簽出的 token 可直接接上 HTTP 用戶端: .. code-block:: python diff --git a/docs/source/Zh/doc/new_features/v62_features_doc.rst b/docs/source/Zh/doc/new_features/v62_features_doc.rst index 8c3d9ced5..497962a76 100644 --- a/docs/source/Zh/doc/new_features/v62_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v62_features_doc.rst @@ -43,6 +43,7 @@ ---------- ``AC_rate_limit`` 接受限制器 ``name`` 以及 ``rate`` / ``capacity`` / ``n``,嘗試從該具名 token -bucket(首次使用時建立)取用 ``n`` 個 token,回傳 ``{acquired, tokens, wait}``,讓流程可閘控或 +bucket(首次使用時建立,呼叫給出不同的 ``rate`` 或 ``capacity`` 時重建;executor 與 MCP 共用同一組 bucket, +即 ``named_bucket``)取用 ``n`` 個 token,回傳 ``{acquired, tokens, wait}``,讓流程可閘控或 延後某個動作。同一操作亦以 MCP 工具 ``ac_rate_limit`` 以及 Script Builder 中 **Flow** 分類下的 命令提供。 diff --git a/docs/source/Zh/doc/new_features/v64_features_doc.rst b/docs/source/Zh/doc/new_features/v64_features_doc.rst index f5c2e2fd8..b484c8d56 100644 --- a/docs/source/Zh/doc/new_features/v64_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v64_features_doc.rst @@ -29,10 +29,12 @@ ``SearchIndex.add`` / ``remove`` 以增量方式維護索引;``build`` 索引一個 ``{doc_id: text}`` 對映 (或 ``(id, text)`` 配對)。``search`` 回傳排名後的 ``SearchHit(doc_id, score)`` 結果 —— 預設 -為 BM25(``k1=1.5``、``b=0.75``),或 ``mode="tfidf"``。評分採標準 Okapi 公式, +為 BM25(``k1=1.5``、``b=0.75``),或 ``mode="tfidf"``(對數化詞頻乘上 ``ln(N / df) + 1``, +所以出現在每份文件的詞仍會計分)。BM25 評分採標準 Okapi 公式, ``IDF = ln(1 + (N − df + 0.5) / (df + 0.5))``,因此罕見詞勝過常見詞、詞頻會飽和(``k1``)、長 文件被正規化下調(``b``)。可提供 ``stop_words`` 集合以濾除雜訊詞。詞是任何文字系統中連續的字母與數字(做 casefold),所以 -``登入``、``café`` 會完整索引;``stop_words`` 也以相同方式轉換。結果具決定性(平手以 +``café`` 會完整索引;不以空格分詞的假名、CJK 漢字與諺文連續字串則以相鄰兩字(bigram)索引, +因此能在 ``請先登入系統`` 中找到 ``登入``。``stop_words`` 也以相同方式轉換。結果具決定性(平手以 ``doc_id`` 決定)。 執行器命令 diff --git a/docs/source/Zh/doc/new_features/v66_features_doc.rst b/docs/source/Zh/doc/new_features/v66_features_doc.rst index 87475a22e..646c1d323 100644 --- a/docs/source/Zh/doc/new_features/v66_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v66_features_doc.rst @@ -8,7 +8,9 @@ 支援的規則部分:``FREQ``(DAILY/WEEKLY/MONTHLY/YEARLY)、``INTERVAL``、``COUNT``、``UNTIL``、 ``BYDAY``(含序數如 ``2MO`` / ``-1FR``)、``BYMONTHDAY``(含負數)、``BYMONTH``、``BYSETPOS`` 與 ``WKST``。時間層級部分以及 BYWEEKNO/BYYEARDAY 不在範圍內:``parse_rrule`` 遇到它們、 -或同時給了 ``COUNT`` 與 ``UNTIL`` 時拋出 ``AutoControlException``。純標準函式庫(``datetime`` + +同時給了 ``COUNT`` 與 ``UNTIL``,或出現 RFC 5545 禁止的組合(``WEEKLY`` 搭配 ``BYMONTHDAY``、``DAILY`` / ``WEEKLY`` +搭配有序數的 ``BYDAY``、未知的 ``WKST``)時拋出 ``AutoControlException``。沒有 ``BYMONTH`` 時,``YEARLY`` 的 ``BYDAY`` +序數以整年計算。純標準函式庫(``datetime`` + ``calendar``);時鐘可注入,因此 ``next_occurrence`` 具決定性。不匯入 ``PySide6``。 無頭 API @@ -33,7 +35,9 @@ ``parse_rrule`` 接受帶或不帶 ``RRULE:`` 前綴的規則,回傳凍結的 ``Recurrence``。``occurrences`` 產生以 ``dtstart`` 為錨點的 datetime(其時刻與時區會套用到每一次發生),受 ``COUNT`` / ``UNTIL`` -(或 ``count=`` / ``until=`` 覆寫)及安全上限約束。僅含日期的 ``UNTIL`` 會包含整天。 +約束;``count=`` / ``until=`` 參數再進一步收窄(取較小者),沒有次數限制的規則由 ``max_iter`` 設上限。序列在 +9999-12-31 結束,或在連續 400 年(間隔較長時等比放大)沒有任何發生時結束,所以永遠不會成立的規則也會停止。 +僅含日期的 ``UNTIL`` 會包含整天。 ``next_occurrence`` 回傳在 ``now`` 當下或之後的第一次發生。 執行器命令 diff --git a/docs/source/Zh/doc/new_features/v76_features_doc.rst b/docs/source/Zh/doc/new_features/v76_features_doc.rst index 82ed4a31d..9c082f2c9 100644 --- a/docs/source/Zh/doc/new_features/v76_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v76_features_doc.rst @@ -29,9 +29,13 @@ W3C Trace Context 傳播 span = child_context(parent) # 相同 trace_id,新的 span_id ``SpanContext`` 是不可變的(``trace_id``、``span_id``、``trace_flags``、``tracestate``)組合。 -``new_root_context`` 鑄造新 trace;``child_context`` 保留 trace id 與繼承狀態但配置新的 span id。 +``new_root_context`` 鑄造新 trace;``child_context`` 保留 trace id 與繼承狀態但配置新的 span id,並清掉 sampled 與 random +以外的旗標位元。 ``parse_traceparent`` / ``format_traceparent`` 來回轉換 version-``00`` 標頭(較新的版本當作 ``00`` 讀取、忽略多出的欄位; -版本 ``ff``、格式不符或全零 ID 拋出 ``TraceContextError``);``parse_tracestate`` / ``format_tracestate`` 處理 vendor 清單。 +版本 ``ff``、格式不符或全零 ID 拋出 ``TraceContextError``,``format_traceparent`` 寫出前也會驗證手工建立的 context); +``parse_tracestate`` / ``format_tracestate`` 處理 vendor 清單。解析時只去掉逗號兩側的空格與 tab、保留值開頭的空格,捨棄鍵或值 +不合文法的成員(空值、含 ``=`` 或 ``,``、超過 256 字元、控制字元),讀滿 32 個成員就停,鍵重複時回傳 ``[]``;格式化遇到這種成員會拋出 +``TraceContextError``,不會寫出去。 ``inject_context`` 寫入標頭;``extract_context`` 將其讀回(不分大小寫),``traceparent`` 缺少或無效時回傳 ``None``,讓接收端依 W3C Trace Context 開一條新的 trace。 diff --git a/docs/source/Zh/doc/new_features/v77_features_doc.rst b/docs/source/Zh/doc/new_features/v77_features_doc.rst index a7be93e61..c8dac6c35 100644 --- a/docs/source/Zh/doc/new_features/v77_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v77_features_doc.rst @@ -24,9 +24,9 @@ dict),因此在 CI 中完全具決定性。 report = validate_rows(rows, schema) ``profile_rows`` 回傳 ``{row_count, columns}``,每欄帶有其筆數、空值數與比例、相異值數、唯一性旗標、 -推斷型別(``int`` / ``number`` / ``bool`` / ``str``)、最常見值與其次數,以及數值欄的 ``min`` / -``max`` / ``mean``。``infer_schema`` 把該剖析轉成既有 ``validate_rows`` 能理解的結構:無空值的欄位 -標為 ``required``,每個非空值皆相異則標為 ``unique``,並帶上數值邊界。傳入明確的 ``columns`` 清單可將 +推斷型別(``int`` / ``number`` / ``bool`` / ``str``,沒有單一型別時為 ``mixed``)、最常見值與其次數 +(``True`` 與 ``1`` 分開計),以及數值欄的 ``min`` / ``max`` / ``mean``(``int`` 欄為精確值,其他以有限值計算)。``infer_schema`` 把該剖析轉成既有 ``validate_rows`` 能理解的結構:無空值的欄位 +標為 ``required``,每個非空值皆相異則標為 ``unique``,並帶上數值邊界;``mixed`` 欄不加 ``type`` 規則。傳入明確的 ``columns`` 清單可將 兩個函式限制在子集。 執行器命令 diff --git a/docs/source/Zh/doc/new_features/v78_features_doc.rst b/docs/source/Zh/doc/new_features/v78_features_doc.rst index bfde1432f..04ed00dbf 100644 --- a/docs/source/Zh/doc/new_features/v78_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v78_features_doc.rst @@ -24,7 +24,7 @@ RFC 9457 Problem Details 解析 # 或把 problem 回應轉成例外: raise_for_problem(response) # 拋出 HttpProblemError -``is_problem`` 檢查 ``Content-Type``(不分大小寫)。``parse_problem`` 回傳 ``ProblemDetails`` +``is_problem`` 檢查 ``Content-Type`` 的媒體類型是否為 ``application/problem+json``(不分大小寫,忽略參數)。``parse_problem`` 回傳 ``ProblemDetails`` (``type`` 預設 ``about:blank``,可轉換時 ``status`` 為整數,所有非註冊鍵收進 ``extensions``), 回應非 problem 文件時回傳 ``None``;當 ``json`` 缺席時會回退去解析 ``text``。``ProblemDetails.summary`` 給出一行描述,``to_dict`` 把文件攤平並併回擴充欄位。``raise_for_problem`` 對 problem 回應拋出 diff --git a/docs/source/Zh/doc/new_features/v79_features_doc.rst b/docs/source/Zh/doc/new_features/v79_features_doc.rst index c90eb9d86..715f478c0 100644 --- a/docs/source/Zh/doc/new_features/v79_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v79_features_doc.rst @@ -24,9 +24,10 @@ Dotenv(.env)解析 ``parse_dotenv`` 略過空白與 ``#`` 註解行,去除選用的 ``export`` 前綴,驗證鍵,並解析值:單引號值為 字面值(僅 ``\'`` 與 ``\\`` 會轉義,與 python-dotenv 相同),雙引號值處理 ``\n`` / ``\t`` / ``\\`` / ``\"`` 轉義,未加引號的值會去除結尾 `` #`` 註解與 -前後空白。加引號的值到收尾的引號為止,後面的註解會略過,值也可以跨多行。``dotenv_values`` 讀取並解析檔案;``load_dotenv`` 把檔案合併進明確的 ``env`` mapping +前後空白;``#`` 只有在空白之後才算註解開頭,所以 ``COLOR=#ff0000`` 保留它的值,``KEY= # note`` 則是空值。加引號的值到收尾的引號為止, +後面的註解會略過,值也可以跨多行,並保留每一行結尾的空白。開頭的 BOM 會略過。``dotenv_values`` 讀取並解析檔案;``load_dotenv`` 把檔案合併進明確的 ``env`` mapping (預設保留既有鍵,除非 ``override``);``dump_dotenv`` 把 mapping 序列化回 ``.env`` 文字,並為需要的值 -加上引號。 +加上引號;遇到解析器讀不回來的鍵(例如含換行或 ``=``)會拋出 ``DotenvError``(同時是 ``AutoControlException`` 與 ``ValueError``)。 執行器命令 ---------- diff --git a/docs/source/Zh/doc/new_features/v81_features_doc.rst b/docs/source/Zh/doc/new_features/v81_features_doc.rst index d60789424..e03ed8239 100644 --- a/docs/source/Zh/doc/new_features/v81_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v81_features_doc.rst @@ -27,7 +27,7 @@ ``add_layer`` 註冊一個具名層;``priority`` 越高越勝出(預設為插入順序,因此後加的層覆蓋先前的)。 ``resolve`` 依優先序由低到高深度合併每一層 —— 巢狀 dict 遞迴合併,而純量與 list 直接取代。``get`` 以 點分鍵從解析後設定讀取並帶預設值;``explain`` 回傳 ``SourceTrace``,標明點分鍵的勝出層(不存在時拋 -``KeyError``)。``deep_merge`` 另以獨立的雙 mapping 輔助函式提供。 +``KeyError``)。``deep_merge`` 另以獨立的雙 mapping 輔助函式提供,結果不與任一輸入共用巢狀值。 執行器命令 ---------- diff --git a/docs/source/Zh/doc/new_features/v82_features_doc.rst b/docs/source/Zh/doc/new_features/v82_features_doc.rst index 9f06841b9..4d86b55da 100644 --- a/docs/source/Zh/doc/new_features/v82_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v82_features_doc.rst @@ -25,7 +25,8 @@ Kolmogorov-Smirnov 雙樣本檢定來做經典的「今天的資料形狀是否 ``psi`` 以 ``reference`` 的分位邊界將 ``current`` 分箱,並加總每箱的 log-ratio 貢獻(分布相同為 0, 分歧越大值越大)。``ks_two_sample`` 回傳最大經驗 CDF 差距與 Kolmogorov 分布的 p 值。``categorical_drift`` 以卡方統計量與 total-variation 距離比較類別頻率。``detect_drift`` 把數值路徑包成一份報告,並以 ``threshold`` -(預設 ``0.25``)給出 ``drifted`` 判定。 +(預設 ``0.25``)給出 ``drifted`` 判定。任一數值樣本含 NaN 時丟出指明索引的 ``ValueError``: +請先移除或補值。 執行器命令 ---------- diff --git a/docs/source/Zh/doc/new_features/v83_features_doc.rst b/docs/source/Zh/doc/new_features/v83_features_doc.rst index eed721466..3f57eae55 100644 --- a/docs/source/Zh/doc/new_features/v83_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v83_features_doc.rst @@ -26,7 +26,7 @@ ``diff_rows`` 為兩個資料列集合建立鍵索引,回傳 ``{added, removed, changed, unchanged}``:``added`` / ``removed`` / ``unchanged`` 是資料列清單,而 ``changed`` 收錄 ``{key, old, new}``(單欄鍵為純量,複合鍵 -為 list)。鍵重複時以最後一列為準。``cell_changes`` 把變更的列展開成 ``{key, column, old, new}`` 記錄。 +為 list)。鍵重複時以最後一列為準;鍵為空或某列缺少鍵欄位時丟出 ``ValueError``。NaN 儲存格視為等於 NaN。``cell_changes`` 把變更的列展開成 ``{key, column, old, new}`` 記錄。 ``summarize_diff`` 統計每個分類的數量。 執行器命令 diff --git a/docs/source/Zh/doc/new_features/v84_features_doc.rst b/docs/source/Zh/doc/new_features/v84_features_doc.rst index ec5c5b9d6..2baf32bf7 100644 --- a/docs/source/Zh/doc/new_features/v84_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v84_features_doc.rst @@ -24,7 +24,7 @@ W3C Baggage 傳播 ``Baggage`` 包裝一個不可變的鍵值對應:``get`` 讀取,``set`` / ``remove`` 回傳新實例,``to_dict`` 匯出 條目。``parse_baggage`` 解析標頭(去除選用的 ``;metadata`` 並拒絕空鍵),``format_baggage`` 將鍵與值 -percent-encode 回標頭值,``inject_baggage`` / ``extract_baggage`` 在請求 dict 上寫入與讀取 ``baggage`` +percent-encode 回標頭值(``Baggage`` 以 ``ValueError`` 拒絕空鍵,因為沒有解析器讀得回來),``inject_baggage`` / ``extract_baggage`` 在請求 dict 上寫入與讀取 ``baggage`` 標頭(讀取不分大小寫)。與 ``trace_context`` 自然搭配,在 trace 之外攜帶脈絡。 執行器命令 diff --git a/docs/source/Zh/doc/new_features/v85_features_doc.rst b/docs/source/Zh/doc/new_features/v85_features_doc.rst index 9a434dee7..c9c547c6a 100644 --- a/docs/source/Zh/doc/new_features/v85_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v85_features_doc.rst @@ -34,4 +34,6 @@ governance 憑證 broker。``resolve_refs_in`` 走訪巢狀 dict/list 並就地 ``AC_resolve_ref`` 把單一 ``ref`` 解析成 ``{value}``;``AC_resolve_refs`` 解析 ``obj`` 內每個參照並回傳 ``{resolved}``。兩者皆以 MCP 工具(``ac_resolve_ref`` / ``ac_resolve_refs``)以及 Script Builder 中 -**Security** 分類下的命令提供。 +**Security** 分類下的命令提供。它們的結果會被記錄(executor 紀錄、MCP 回應),所以遇到 ``secret://`` +會丟 ``SecretRefError``:祕密的值不進這些紀錄。需要值的步驟請引用 ``${secrets.NAME}``,或在 Python +裡呼叫 ``resolve_ref``。 diff --git a/docs/source/Zh/doc/new_features/v87_features_doc.rst b/docs/source/Zh/doc/new_features/v87_features_doc.rst index cc92ff1f0..f27a81e15 100644 --- a/docs/source/Zh/doc/new_features/v87_features_doc.rst +++ b/docs/source/Zh/doc/new_features/v87_features_doc.rst @@ -22,8 +22,10 @@ RFC 8288 Link 標頭與分頁 # 透過注入的 fetch(傳輸 / 卡帶)走訪每一頁: pages = paginate(start_url, fetch, max_pages=50) -``parse_link_header`` 回傳 ``Link`` 清單(``uri``、``rel`` 與所有 ``params``),容許含逗號的引號值與單一 -標頭中的多個連結。``links_by_rel`` 依每個(以空白分隔的)關係索引,``next_url`` 是 ``rel="next"`` 的便利 +``parse_link_header`` 回傳 ``Link`` 清單(``uri``、``rel`` 與所有 ``params``),以 RFC 8288 附錄 B 的演算法讀取標頭: +引號值保留其中的逗號、分號與跳脫字元,未加引號的值一直到下一個 ``;`` 或 ``,`` 為止(所以 ``title=x