Skip to content

Close the approved Progress items: input, windows and DPI, scopes, signing, roles, MCP roots, USB ids, package gate - #509

Merged
JE-Chen merged 39 commits into
mainfrom
feat/progress-sweep
Oct 8, 2026
Merged

JE-Chen merged 39 commits into
mainfrom
feat/progress-sweep

Conversation

@JE-Chen

@JE-Chen JE-Chen commented Oct 8, 2026

Copy link
Copy Markdown
Member

Closes the items in Progress.md that could be closed on this machine: plan A of docs/superpowers/plans/ (A1, A3–A6, A8–A14; A2 and A7 landed with #501) plus the package-gate default and the mypy 2.4 gap. This branch contains #502; merge that first and this diff shrinks to the sweep.

What changes for users

Breaking or behaviour-changing (all in CHANGELOG.md):

  • Package gate refuses by default. Allow with executor.allow_packages(...), JE_AUTOCONTROL_ALLOWED_PACKAGES or je_auto_control run --allow-package.
  • A file a remote-desktop host pushes lands below the viewer's download directory; dest_path is relative now.
  • Variables no longer survive a run started through execute_action_with_vars or any server / scheduler / trigger entry point. Direct executor.execute_action from Python is unchanged.
  • Windows is per-monitor DPI aware. Coordinates, regions, layouts and templates recorded on a monitor whose scale differs from the primary's have to be recorded again.
  • Keyboard and mouse: capitals and is_shift work on Windows / X11, CR LF is one Enter, mouse_scroll defaults to scroll_up on every platform, coordinates are rounded.
  • Windows window management: focus_window raises when the window did not become foreground, cloaked and zero-area windows are not listed, post_key sends one message per character, saved layouts stop drifting.
  • cryptography>=50.0.0; the pytest11 entry point is the top-level je_auto_control_pytest.

Opt-in, off until configured: Ed25519 action signatures (JE_AUTOCONTROL_ACTION_SIGNING_PUBLIC_KEY / _PRIVATE_KEY), roles for REST and MCP HTTP (JE_AUTOCONTROL_RBAC_USERS), roots for MCP file arguments (JE_AUTOCONTROL_MCP_PATH_ROOTS).

Also: USB passthrough replies paired by an echoed request_id; remote-desktop hosts and viewers have owners; the Anthropic agent backends stop rewriting sent turns; macOS click count, minimised-window lookup and point-based capture; template / OCR fixes; mypy 2.4 (supersedes #506).

Verified

  • Full headless suite, Windows, Python 3.14, PySide6 6.11.2: 11377 passed, 69 skipped.
  • ruff, bandit, radon cc -nc: clean. typing_contract_verify.py: 0 failing modules on all three targets with mypy 2.3.0 and 2.4.0.
  • uv build: the wheel and the sdist contain je_auto_control_pytest.py and the entry point.

Not verified — read before relying on it

Everything was tested against fakes. Nothing was typed, clicked, focused or captured on a real desktop, no Mac was used, no mixed-DPI desktop, no real MCP client with roles or roots, no live Anthropic run, no real USB device. Progress.md lists each of these under three sections (input and windows, macOS, follow-ups). The macOS CI squares run two tests here for the first time (test_window_backend_macos_real.py, the darwin-only cases of test_osx_mouse_click_state.py).

Left in Progress.md

Plans B, C (except C4), D, E, G, H; GUI plan F3 / F4 and the F follow-ups; Windows arm64 and libei (upstream); the computer-use default (needs a live run); the intermittent Python 3.10 segfault.

JE-Chen and others added 30 commits October 8, 2026 22:02
…t themes, and tabs built on first open

Forty-five of the 48 tabs could only be reached through View > Tabs > category,
the window built every one of them before showing three, and its look was a
third-party sheet that could not be adjusted. The panel lists every feature
and searches it (Ctrl+K), the theme comes from one set of tokens, and a tab's
module is imported when the tab is opened.

The qt_material guard test goes with the import it guarded, so the main-window
tests it kept skipped in CI now run there.
Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](python/mypy@v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 2.4.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
…longer imports the facade

Every pytest run in an environment with this package installed imported about
1,350 modules through the pytest11 entry point, because the plugin was a
submodule of the package. je_auto_control_pytest imports only pytest; the old
import path re-exports the same objects.
…nstead

Replacing older screenshots in turns the Messages API had already seen missed the prompt cache on every step and, on models whose thinking blocks are bound to the conversation before them, got the request refused with HTTP 400. Both Anthropic backends now leave sent turns alone and, past the screenshot limit, open a new history of one summary message (goal, executed actions and outcomes) plus the current screenshot. The OpenAI backend keeps pruning in place.
AC_sign_action_file signed with the per-user HMAC key that verification also read, so anyone who could run an action could sign a file and then run it. Version-2 signatures are Ed25519: the signing machine holds the private key, execution endpoints hold only the public key, and an endpoint configured that way refuses to sign or mint a key pair instead of falling back to HMAC. HMAC sidecars stay the default when no key pair is configured and are accepted beside one only in an explicit migration mode. A missing cryptography wheel is now CryptographyUnavailableError, inside the framework family.
The 48.0.1 floor still admitted GHSA-g6cj-pr64-35w5 (PKCS#7 EnvelopedData decryption oracle, >=44.0.0,<50.0.0). The maintainer accepted the cost: 49.0.0 dropped the macOS universal2 wheel, so Intel Macs now build cryptography from source. The Windows arm64 marker is unchanged.
The warning release has been out for two releases, so AC_add_package_to_executor and AC_add_package_to_callback_executor now refuse a package nobody allowed instead of importing it with a DeprecationWarning. Hosts with no Python of their own allow packages with JE_AUTOCONTROL_ALLOWED_PACKAGES (read where the shared manager is created, so every entry point honours it) or je_auto_control run --allow-package; executor.set_allow_arbitrary_packages(True) remains the explicit opt-out.
execute_action_with_vars seeded the module executor and never cleared it,
and REST, MCP, the socket server, the scheduler, triggers, hotkeys, ChatOps
and voice commands all ran on that executor, so one caller's ${user}
resolved in the next caller's run instead of failing as unknown.

execution_scope() binds a fresh VariableScope to the module executor for
one run (contextvars, restored in finally); those entry points open one.
Direct executor.execute_action() calls keep the process scope, which the
GUI's Variables tab relies on.

AC_run_dag ran its nodes on the module executor from pool threads, so
inside an AC_parallel branch they read the wrong scope; the runner now
captures the calling executor and scope. A parallel branch starts from a
copy of its parent's variables instead of an empty scope.
…lick

macOS applications read kCGMouseEventClickState off the event instead of timing two clicks, and the osx backend never wrote it, so click_mouse(clicks=2) arrived as two single clicks. The n-th click of a run now sets the field to n on both press and release, unless the interval is longer than the system double-click interval. A single click and every other platform make the same backend call as before.
…tored

Every single-window lookup went through Quartz's on-screen list, which a minimized window is not in: after minimize() the window vanished from list_windows and restore() raised the Accessibility refusal on a Mac that had granted it. _info_for now asks with kCGWindowListOptionIncludingWindow, list_windows appends the off-screen windows whose accessibility element reports AXMinimized (as the Windows backend keeps iconified windows), and is_minimized no longer answers True for a window Quartz does not know.
…cutting each other's sessions

Quick Connect, the viewer tab and AC_remote_connect wrote the same registry slot and each cleared it first. A connect on one side cut the other's session without telling it: its popup stayed on the last frame and its Disconnect then ended a session it never opened. The registry now records an owner per occupant, panels read and close only what they opened, and a replaced owner is told so it closes its window. The script-side commands keep acting on the active host or viewer whoever opened it.
Replies were matched by kind only, so a reply that arrived after its
request timed out completed the next request of that kind: open(bbbb)
bound the claim opened for aaaa, and a bulk read returned the previous
read's data.

The viewer now sends an optional request_id in every request and the host
echoes it in every reply, so a late reply is discarded by its tombstone and
nothing else is. A host that does not echo ids cannot be disambiguated, and
dropping the next reply would lose a correct answer whenever the late one
never comes, so after a timeout the viewer refuses further requests of that
kind with UsbClientDesynchronized instead of guessing.
Windows' foreground lock refuses a background process often, and focus_window, show_window and the z-order driver reported success anyway, so the caller's next keystrokes went to whatever window the user had active. focus_window now confirms the window is in front and raises otherwise; the others return what Win32 answered.

post_key posts a printable character as WM_CHAR alone and other keys with a real lParam: the old trio was translated into three characters by the target. post_key_to_window resolves enter/esc through the platform's own key names. Cloaked and zero-area windows are no longer listed. A saved layout stores the rectangle MoveWindow positions, so restoring no longer moves a window 7 px per round, and snap/grid/cascade use the work area. wait_for_window clamps its poll and never sleeps past the timeout.
The process asked for system DPI awareness, so Windows virtualised any monitor scaled differently from the primary one: scaled coordinates and a blurred, resized capture. It now asks for per-monitor-v2 first and falls back, once, without raising when the awareness was already fixed.

pil_screenshot's region path (and with it screenshot, AC_screenshot, keyword_screenshot and capture_window) cropped a primary-monitor capture, so a region on a monitor left of or above it was black; it is cut from the whole virtual desktop on Windows now. mark_screen renders on that desktop too and reports the image's origin. grab_logical clips a region to the desktop and rejects an empty one with a framework error instead of handing a matcher black padding.

On macOS the frame is built per display from Quartz's bounds in points, scaled down and stitched, so a Retina capture is no longer twice the mouse's coordinates and other displays are included. Tested with fakes only; not run on Retina hardware.
…loor negative centres

A template under a non-ASCII folder was unreadable through cv2.imread, a 2-D or PIL L template raised cv2.error that no caller catches, a target starting at the end of a long OCR box was dropped with that box, and int() cut the centre of a hit left of the primary monitor toward zero.
…oll up for a positive count everywhere

write() typed capitals in lower case on Windows and X11 and is_shift did nothing outside macOS; CR LF pressed Enter twice; mouse_scroll's default direction made a positive count go down on X11 and up elsewhere; a NaN scroll point was clamped instead of refused and fractional coordinates were cut toward zero. Unicode typing sent line breaks and Tab as code points, a dead-key Shift level was labelled with the unshifted character, ISO keys had no label, the layout prototypes were set on the shared user32, and an unnamed clipboard format was called 'None'.
…in CI

mypy 2.4 resolves the 'sys.platform not in [...]' guards against the target,
so on a foreign target the rest of those modules is unreachable and their
unannotated names have no type for importers. The contract passes on 2.3 and
2.4 for all three targets.
…d files in the viewer's download directory

An MCP tool's file argument reached any file the process could open, even in
read-only mode: ac_load_dotenv returned any file as KEY=VALUE. ac_resolve_ref
read any environment variable and any file. Both stay unrestricted unless an
operator opts in, so existing deployments are unchanged:
JE_AUTOCONTROL_MCP_PATH_ROOTS and JE_AUTOCONTROL_MCP_PATH_ROOTS_FROM_CLIENT
give the roots, JE_AUTOCONTROL_MCP_ENV_REF_ALLOW the env:// allowlist. The
schema says which arguments are paths ("format": "path"), by meaning rather
than by name, so ac_json_query's JSONPath is left alone.

A remote desktop viewer wrote a host-pushed file wherever the host said. A
viewer cannot vouch for the host it dialled, so dest_path is now relative to
the viewer's download directory and anything leaving it fails the transfer.
The host side keeps trusting its token holders.
…igured

utils/rbac had users, roles and token checks that nothing consulted: both servers compared one shared token, so every caller could do everything and the audit trail could not say who had. RBAC is now opt-in through JE_AUTOCONTROL_RBAC_USERS or a user_store argument; without it the shared token behaves exactly as before. With it a token is one user's, each route, tool and privileged AC_* command needs a capability of that user's role, tools/list agrees with tools/call, and audit entries carry the user id. Signing an action file gets its own capability so that running actions is not approving them.
…ackend can

focus_window's new check compared the platform's foreground id with the id it
had focused; on macOS and X11 a mismatch does not mean the request was
refused, so the check is limited to the Windows backend. Progress.md keeps
only what was not verified or not done; the command stub and the documented
counts are regenerated.
@codacy-production

codacy-production Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 1350 complexity · 1 duplication

Metric Results
Complexity 1350
Duplication 1

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

…ery misses it, and settle the analyser findings

The macos-14 runner showed the by-id Quartz query returning nothing for a
window the same process had just minimised. The DAG runner's default no
longer rebinds a parameter through an import, which the analyser read as a
possible None.
@sonarqubecloud

sonarqubecloud Bot commented Oct 8, 2026

Copy link
Copy Markdown

@JE-Chen
JE-Chen merged commit 9106ef3 into main Oct 8, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant