Skip to content

Merge dev: stoppable MCP calls, lazy start-up tabs, media retirement ownership - #552

Open
JE-Chen wants to merge 133 commits into
mainfrom
dev
Open

JE-Chen wants to merge 133 commits into
mainfrom
dev

Conversation

@JE-Chen

@JE-Chen JE-Chen commented Nov 12, 2026

Copy link
Copy Markdown
Member

Why

dev is 58 commits ahead of main (2026-11-10 to 2026-11-12). This PR brings them to main and runs the checks that only run on pull requests into main, in particular the macos-capabilities job of platform-smoke.yml. That job has not yet confirmed that macos-15 grants the same TCC permissions test/verify/macos_verify.py expects (Progress.md › "macos-15 上的 TCC 權限還沒被 gate 驗過"). If it reports different grants, re-measure with --measure and update EXPECTED and docs/CAPABILITY_MATRIX.md rather than loosening the gate.

Changes

MCP

  • Every tools/call runs as a stoppable executor run (mcp-…). notifications/cancelled stops an ac_execute_actions list before its next action and wakes ac_wait_for_image / ac_wait_for_pixel mid-poll; ac_stop_execution ends MCP calls too.
  • The tools/call path moved out of server.py (745 → 627 lines) into _tool_calls.py; MCP adapters were split into strict domain modules.
  • Reviewed MCP capabilities are enforced through refreshed invocation.

GUI

  • Only the first of the three start-up tabs is built at start; Script Builder and Remote Desktop are built when first selected. Offscreen start-up: 3.2 s → 1.7 s, 172 MB → 111 MB.
  • Report, Screenshot and Image Detection got their own lifecycles and background tasks; GUI task cancellation reaches headless operations; focus waits honour executor stop.
  • Remote pages release without retaining callback owners; WebRTC statistics start and stop without waiting on the GUI thread.

Remote desktop / WebRTC

  • Peers retain media constructor and teardown failures, session retirement binds to the original peer and source, LAN browser cleanup and file senders keep their owners until the work actually finishes.

Security

  • REST host-data reads (operational records, USB enumeration, inspector statistics, process metrics) and the matching executor reads require read_data.

Executor and mobile

  • The result hook follows AC_parallel branches, so the Script Builder offers a branch's token as its own value.
  • iOS swipes no longer press before moving; an Android pinch past the screen edge raises DeviceError. Both were found by reading the uiautomator2 3.7.1 and facebook-wda 1.5.4 sources.
  • Reading the iOS pasteboard needs allow_app_switch=True, because facebook-wda switches to WebDriverAgent through Siri to read it.

Platform

  • Default Wayland EI input runs behind an owned worker; Linux presence endpoints bind through private helpers.
  • Bounded internal Windows process-memory observations behind reviewed identity attestation (internal only, no public entry point).
  • Config-sync asset sync refuses, with a clear error, paths Windows cannot create without LongPathsEnabled.

Docs and records

  • Progress.md reconciled with the tree: finished, stale and superseded items removed and recorded in docs/updates/2026-11-b.md (U-20261111-*, U-20261112-01 to -14).

Compatibility

See CHANGELOG.md › Unreleased. The ones a caller may notice:

  • A stopped MCP call is answered as a tool error (ExecutionStopped), a cancelled one with -32800; ac_list_executions lists MCP calls in progress.
  • Code that expected the Script Builder and Remote Desktop widgets to exist right after AutoControlGUIWidget() gets them on first access.
  • iOS swipe ignores duration_s; AC_ios_get_clipboard needs allow_app_switch=True.
  • REST and executor reads listed above refuse viewers without read_data.

Testing

  • AutoControl Dev CI on dev head 65116dbc: all four pytest-headless squares (Windows 3.11 / 3.14, Ubuntu 3.14, macOS 3.11) passed.
  • Locally on Windows (Python 3.14.7): full headless suite 18,507 passed, 193 skipped, 2 failed in test_agent_transfer_payloads.py, which fail the same way on the commit before these changes and pass in CI.
  • Typing contract on win32 / linux / darwin and strict Sphinx clean.

On merge

pyproject.toml on main declares 1.2.1, which is tagged, so scripts/stable_release.py releases 1.2.2.

The advertiser stop logged a pending or uncertain retirement at debug
level and dropped the advertiser that still owned the name, and a
recording whose finalising raised left Record disabled with no outcome
shown. The advertiser now goes to the stop custody, and the recording
says it may be incomplete.
The Browse LAN dialog joined zeroconf's thread on the GUI thread,
swallowed a failed stop, and handed the browser a bound method that
kept the dialog alive. The browser now reports through a weak lease,
and its stop runs on an application-owned queue whose failures still
reach the stop custody after the dialog is gone.
A cancellation that cuts a media cleanup short carries the original
owners still holding resources, but the GUI kept only the outer stop.
Each attached owner is now kept by the stop custody with its own stop.
Reading a media owner's live retirement status can wait on the lock
its native cleanup holds. The custody now keeps the status the error
captured when it was raised, and Diagnostics shows that instead.
A full stop waits on native work under the owner's lock, and a caller
whose authority was revoked may never run it; until then the owner kept
admitting audio, reports, sessions and input. close_admission() only
stops admission, with no native call or cleanup lock, and StopQueue
calls it on every owner before queuing the stop.
A GUI task's Stop or deadline cancelled its scope, but the HTTP helper
and the signaling long-polls never looked at it, and stopping the WebRTC
host left its publish loop waiting up to ten minutes for an answer.
perform_call now checks the scope around each request, and the waits
check their stop between polls.
A Qt connection holds its slot from C++, out of the collector's sight.
The Script Builder connected a lambda over self to each of its 714
Add-step actions, so every tab built lived until exit, where Qt's
teardown released the closure inside a destructor and deleted the tab
a second time. Those slots, and the similar ones in four other tabs,
are now methods or weak references.
The pin to 6.11.2 waited on finding what crashed the test process at
interpreter exit under 6.12. That was tabs kept alive by slots holding
themselves, fixed on dev; this branch runs the full matrix on 6.12.0
before the pin moves.
Every 3.12 to 3.14 square passed and exited cleanly on 6.12.0, but both
3.11 squares still abort: a None refcount underflow that is harmless
where None is immortal. The pin now takes 6.12.0 only from 3.12 on.
The two assertions on the crash-dump step spelled LocalDumps\python.exe
and autocontrol-native\dumps with a single backslash, which Python 3.11
reports as invalid escape sequences.
Trial PySide6 6.12.0 after the exit-crash fix
The trial ran clean on every 3.12+ square, so the decision is settled;
what remains is unifying the pin once a 6.12.x runs clean on 3.11.
Dependabot alert 177 flags dnspython below 2.9.2 in uv.lock. The relock
also records the gui extra's split PySide6 pin, which the lock still
showed as a single 6.12.0.
817 commands had no entry in COMMAND_CAPABILITIES and relied on the
submitting surface without anyone having decided that was right. Each
was reviewed against the admin-only state the table protects; none
needed raising, and a test now fails on a command in neither table.

The review also found ac_journal_read's drive_input override sitting
above the DATA_TOOLS spread, where it was silently overwritten.
The approver was whatever name the caller passed, so under RBAC one
operator could file a request and approve it as somebody else, which
defeats segregation of duties. Outside RBAC the names work as before.
They already passed disallow_untyped_defs and disallow_any_generics on
all three target platforms, real Qt and aiortc types included, but the
shrink-only lists left them free to regress.
Each had a single missing annotation or bare generic between it and
the strict flags. Four whose correct annotation exposes a real type
error, and the modules that alias ImageSource to Any, stay listed.
These had two or three missing annotations or bare generics each. Seven
whose correct annotation exposes a real type error stay listed, as do
the image modules that alias ImageSource to Any.
These had four to six missing annotations or bare generics each. The
image modules that alias ImageSource to Any, and vlm_tab whose correct
annotation exposes a real mismatch, stay listed.
These had seven to ten missing annotations or bare generics each. The
unscoped ignores on optional backend imports go: the forced-Any
override already covers those modules.
Each matching module spelt its own ImageSource = Any, which the typing
contract allows only in a registered adapter. One adapter now owns the
alias, so the modules that otherwise passed can join the tier.
Both shapes are what the code already narrows on, so the two locator
modules can join the strict tier. MouseKeycode keeps its Any: typing it
per platform exposes mismatched key tables in the wrapper layer.
Mostly agent_runtime. The cv2 error decorator now keeps the signatures
of what it wraps, and the signaling routes declare the response shapes
they already returned.
Annotating get_pixel let the contract sweep exercise this tool, and the
sample it drew from the schema had one channel, which the handler has
always refused. The schema now says so.
The last annotation stage: 17 more modules join the strict tier and the
not-yet-strict list is empty. Typing the WebRTC control dispatch showed
that a peer sending a list as the message type crashed the callback.
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
E Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

This branch has not been deployed

No deployments
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