Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/platform-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Platform smoke

on:
push:
branches: ["main", "dev"]
pull_request:
branches: ["main", "dev"]

permissions:
contents: read

jobs:
stable-api:
strategy:
fail-fast: false
matrix:
os: [windows-2022, ubuntu-22.04, macos-14]
python-version: ["3.10", "3.14"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: python -m pip install -e .
# The X11 backend connects to a display at import time, so Linux
# runs need a virtual one.
- name: Install a virtual display (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y xvfb
- name: Import stable API and generate platform-neutral code
shell: bash
run: >-
${{ runner.os == 'Linux' && 'xvfb-run -a' || '' }}
python -c "import je_auto_control.api as ac;
compile(ac.generate_code([['AC_screen_size']], style='actions'),
'<generated>', 'exec')"
- name: Create headless diagnostic bundle
shell: bash
run: >-
${{ runner.os == 'Linux' && 'xvfb-run -a' || '' }}
python -c "from je_auto_control.api import
FailureBundleOptions, create_failure_bundle;
create_failure_bundle('platform-smoke.zip',
options=FailureBundleOptions(screenshot=False))"
- uses: actions/upload-artifact@v4
if: always()
with:
name: platform-smoke-${{ matrix.os }}-${{ matrix.python-version }}
path: platform-smoke.zip
if-no-files-found: warn
32 changes: 30 additions & 2 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ permissions:
contents: read

jobs:
dependency-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/dependency-review-action@v4

lint:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -81,7 +90,26 @@ jobs:
# for any sub-package the snapshot doesn't include
# (admin, usb, remote_desktop, vision, …).
pip install -e .
pip install ruff==0.15.14 bandit==1.9.4 pytest==9.0.3 pytest-timeout==2.4.0 pytest-rerunfailures==15.1 PySide6==6.11.1
pip install ruff==0.15.14 bandit==1.9.4 pytest==9.0.3 pytest-timeout==2.4.0 pytest-rerunfailures==15.1 pytest-cov==7.0.0 PySide6==6.11.1

- name: Run headless pytest suite
run: pytest test/unit_test/headless/ -v --tb=short --timeout=120
run: >-
pytest test/unit_test/headless/ -v --tb=short --timeout=120
--cov=je_auto_control --cov-report=term-missing
--cov-report=xml --cov-fail-under=35

- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.python-version }}
path: coverage.xml

typing-stable-api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -e . mypy
- run: mypy je_auto_control/api je_auto_control/utils/failure_bundle
62 changes: 62 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Release

on:
push:
tags: ["v*"]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: python -m pip install --upgrade build twine
- name: Verify tag matches package version
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
python - <<'PY'
import os, tomllib
with open("pyproject.toml", "rb") as handle:
version = tomllib.load(handle)["project"]["version"]
if os.environ["RELEASE_TAG"] != f"v{version}":
raise SystemExit(f"tag {os.environ['RELEASE_TAG']} != v{version}")
PY
- run: python -m build
- run: python -m twine check dist/*
- name: Smoke-test the built wheel
run: |
python -m venv /tmp/wheel-test
/tmp/wheel-test/bin/pip install dist/*.whl
/tmp/wheel-test/bin/python -c "import je_auto_control.api"
- uses: actions/attest-build-provenance@v2
with:
subject-path: "dist/*"
- uses: actions/upload-artifact@v4
with:
name: python-distributions
path: dist/

publish:
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/je-auto-control
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: python-distributions
path: dist/
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
3 changes: 2 additions & 1 deletion .github/workflows/stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ jobs:

publish:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
# Publishing moved to release.yml: immutable v* tags + Trusted Publishing.
if: ${{ false }}
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,8 @@ dmypy.json
/.claude/
/.claude
/.idea

# Local test/smoke artifacts
.test-tmp/
bundle-smoke.zip
platform-smoke.zip
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Changelog

This file records user-visible compatibility changes. Detailed development
notes remain in `WHATS_NEW.md`.

The format follows Keep a Changelog. Until 1.0, breaking changes are permitted
only when documented here with a migration path.

## Unreleased

### Added

- Stable, headless `je_auto_control.api` façade.
- Portable `autocontrol.failure-bundle/v1` diagnostic archives and CLI command.
- Public API lifecycle, capability matrix, security policy, coverage and type
checking configuration.

### Changed

- Releases are prepared from version tags and use PyPI Trusted Publishing.

### Deprecated

- New integrations should avoid the eager, historical top-level import surface
and import stable entry points from `je_auto_control.api`.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ No feature is complete unless it can be driven entirely without the GUI **and**
- **Re-export from the package facade**: add the public functions / classes to `je_auto_control/__init__.py` and its `__all__` so `import je_auto_control as ac; ac.<feature>(...)` works out of the box.
- **Executor command coverage**: wire an `AC_*` command into `utils/executor/action_executor.py` so the feature is usable from JSON action files, the socket server, the scheduler, and the visual script builder — all without Python glue.
- **GUI tab or control is a thin wrapper**: the Qt widget must only translate user input into calls on the headless core. It must not contain business logic that would be unreachable headlessly.
- **Tab commands live in the Actions menu, not in-tab buttons**: the main window is menu-driven. A tab keeps only its inputs, tables, and result/status views; its commands surface through the window-level **Actions** menu. Core tabs declare `(label_key, handler)` pairs at registration in `gui/main_widget.py`; feature tabs expose a `menu_actions()` method returning the same shape. Script Builder and Remote Desktop are the only exempt tabs (interactive panel layouts). `test/unit_test/headless/test_actions_menu_gui.py` guards this contract — a new tab without registry actions or a `menu_actions()` hook fails CI.
- **The top-level package stays Qt-free**: `import je_auto_control` MUST NOT import `PySide6`. The GUI entry point is loaded lazily inside `start_autocontrol_gui()`. Verify with:

```python
Expand Down
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,10 @@ All per-release notes have moved to **[WHATS_NEW.md](WHATS_NEW.md)**.
- **WebRTC Packet Inspector** — process-global rolling window of `StatsSnapshot` samples (default 600 / ~10 min @ 1Hz) fed by the existing WebRTC stats pollers. Per-metric `last/min/max/avg/p95` for RTT, FPS, bitrate, packet loss, jitter
- **USB Device Enumeration** — read-only cross-platform device listing. Tries pyusb (libusb) first; falls back to platform-specific (Windows `Get-PnpDevice`, macOS `system_profiler`, Linux `/sys/bus/usb/devices`). Phase 2 passthrough builds on this (see below)
- **System Diagnostics** — single-command "is everything OK?" probe across platform, optional deps, executor command count, audit chain, screenshot, mouse, disk space, REST registry. CLI exits 0 if all green / 1 otherwise; REST `/diagnose`; severity-tagged GUI tab
- **Stable API & Failure Bundles** — versioned, lazy `je_auto_control.api` façade for new integrations (`execute_action`, `generate_code`, `run_diagnostics`, failure bundles) with a documented [lifecycle policy](docs/API_LIFECYCLE.md). Portable `autocontrol.failure-bundle/v1` diagnostic ZIPs: manifest + redacted context/events/log tail, optional screenshot and diagnostics, best-effort collectors, atomic write. CLI `je_auto_control failure-bundle out.zip`; `codegen --failure-bundle` wraps generated pytest in automatic failure diagnostics
- **USB Hotplug Events** — polling-based hotplug watcher (`UsbHotplugWatcher`) with bounded ring buffer + sequence-numbered events; `GET /usb/events?since=N` lets late subscribers catch up. GUI auto-refresh toggle on the USB tab.
- **OpenAPI 3.1 + Swagger UI** — `GET /openapi.json` (auth-gated, generated from the live route table) + `GET /docs` (browser Swagger UI with bearer token bar). Drift test in CI catches new routes added without metadata.
- **Configuration Bundle** — single-file JSON export/import of user config (admin hosts, address book, trusted viewers, known hosts, host service, IDs). Atomic write with `<name>.bak.<timestamp>` backups; CLI `python -m je_auto_control.utils.config_bundle export|import`; `POST /config/{export,import}`; GUI buttons on the REST API tab.
- **Configuration Bundle** — single-file JSON export/import of user config (admin hosts, address book, trusted viewers, known hosts, host service, IDs). Atomic write with `<name>.bak.<timestamp>` backups; CLI `python -m je_auto_control.utils.config_bundle export|import`; `POST /config/{export,import}`; export/import commands on the REST API tab's Actions menu.
- **USB Passthrough (opt-in)** — let a remote viewer use a USB device physically attached to the host, over a WebRTC `usb` DataChannel. Wire-level protocol (11 opcodes incl. `RESUME`, CREDIT-based flow control, 16 KiB payload cap with EOF fragmentation for oversize transfers). All eight original open questions resolved: reliable-ordered channel, LIST-over-channel (ACL-filtered), per-claim credits, Linux kernel-driver detach/reattach, and ACL **HMAC-SHA256 integrity** (fail-closed on tamper; pluggable key — Windows DPAPI or passphrase vault). **Backends:** `LibusbBackend` (production), `WinusbBackend` (ctypes) and `IokitBackend` (native IOKit enumeration + libusb transfers) — Windows/macOS *hardware-unverified*; `default_passthrough_backend()` picks per-OS. Viewer-side blocking client (`control/bulk/interrupt_transfer`, `list_devices`, `resume`); in-process `UsbLoopback` so one machine can share + use a device through the full stack. **Wired into WebRTC** host/viewer (`viewer.usb_client()`) plus claim **resume tokens** that survive a reconnect. Persistent ACL (default deny, mode 0600) with host-side prompt dialog, abuse **rate-limit / lockout**, and tamper-evident audit integration. Five driving surfaces: AnyDesk-style **GUI panel** (share + ACL allow/block + local/remote use), `AC_usb_*` executor commands (JSON / socket / scheduler), **REST** `/usb/...`, first-class **MCP** `ac_usb_*` tools, and the Python API. Default off — opt-in via `enable_usb_passthrough(True)` or `JE_AUTOCONTROL_USB_PASSTHROUGH=1`; default-on still pending Phase 2e external security sign-off + real-hardware verification.
- **Observability (Prometheus + OpenTelemetry)** — stdlib-only `Counter` / `Gauge` / `Histogram` registry with a tiny built-in HTTP exporter on `/metrics`, plus an OpenTelemetry-compatible tracer that upgrades to real OTel spans when the SDK is installed. The executor and agent loop emit `autocontrol_action_calls_total{action,outcome}`, `autocontrol_action_duration_seconds`, and `autocontrol_agent_steps_total{tool,outcome}` automatically — drop the URL into a Prometheus scrape config and you have a Grafana dashboard with zero per-script wiring.

Expand Down Expand Up @@ -546,8 +547,8 @@ ac.run_from_description("open Notepad and type hello", executor=executor)
| `AUTOCONTROL_LLM_BACKEND` | `anthropic` to force a backend |
| `AUTOCONTROL_LLM_MODEL` | Override the default model (e.g. `claude-opus-4-7`) |

GUI: **LLM Planner** tab — description box, `QThread`-backed *Plan*
button, action-list preview, and a *Run plan* button.
GUI: **LLM Planner** tab — description box and action-list preview;
*Plan* (`QThread`-backed) and *Run plan* live in the window's Actions menu.

### Runtime Variables & Control Flow

Expand Down Expand Up @@ -575,7 +576,8 @@ commands, scripts can drive themselves from data without Python glue:

`AC_if_var` operators: `eq`, `ne`, `lt`, `le`, `gt`, `ge`, `contains`,
`startswith`, `endswith`. GUI: **Variables** tab — live view of
`executor.variables` with single-set, JSON seed, and clear-all controls.
`executor.variables`; single-set, JSON seed, and clear-all run from the
window's Actions menu.

### Remote Desktop

Expand Down Expand Up @@ -1099,8 +1101,9 @@ for run in default_history_store.list_runs(limit=20):
print(run.id, run.source, run.status, run.artifact_path)
```

The GUI **Run History** tab exposes filter/refresh/clear and
double-click-to-open on the artifact column.
The GUI **Run History** tab shows the runs table with
double-click-to-open on the artifact column; filter, refresh, and
clear run from the window's Actions menu.

### Report Generation

Expand Down Expand Up @@ -1357,6 +1360,16 @@ Or from the command line:
python -m je_auto_control
```

The main window is menu-driven: tabs hold only their inputs, tables,
and result views, and every tab's commands live in the window-level
**Actions** menu, which rebuilds for the active tab. **View → Tabs**
shows or hides any of the ~48 registered tabs, grouped by category
(Core / Editing / Detection & Vision / Automation Engines / System);
the default layout opens with just Record, Script Builder, and Remote
Desktop. **View → Text Size** offers auto/preset font scaling, and the
**Language** menu (English / 繁體中文 / 简体中文 / 日本語) retranslates
the whole window live.

---

## Command-Line Interface
Expand Down Expand Up @@ -1438,6 +1451,11 @@ python -m pytest test/integrated_test/

### Project Links

- [Capability and platform matrix](docs/CAPABILITY_MATRIX.md)
- [Public API lifecycle and deprecation policy](docs/API_LIFECYCLE.md)
- [Security policy](SECURITY.md)
- [Compatibility changelog](CHANGELOG.md)

- **Homepage**: https://github.com/Intergration-Automation-Testing/AutoControl
- **Documentation**: https://autocontrol.readthedocs.io/en/latest/
- **PyPI**: https://pypi.org/project/je_auto_control/
Expand Down
Loading
Loading