mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-04 13:51:13 +00:00
* feat(cua-driver): sync vendored cua-driver from 0.6.8 to 0.7.0 Upstream 0.7.0 brings action-time modality (get_window_state always returns AX tree + screenshot, capture_mode retired), honest verification (every action returns verified/path/effect/escalation), browser page (CDP) tools, desktop-scope (capture_scope=window|desktop, get_desktop_state, screen-absolute click/scroll), and Rust-only consolidation (Swift backend retired). Cherry-pick patch changes: - #2025 (X11 synthetic click): retired — superseded by 0.7.0 honest-verification - #2035 (session revive): retired — superseded by 0.7.0 revive_session/loud-reject/is_session_lifecycle_tool - #2036 (EAGAIN socket write retry): re-injected — merged upstream post-0.7.0 tag, EAGAIN os error 35 is not acceptable - #2021 (null/empty-title windows + resolve_uwp_host_window): retained, adapted to 0.7.0's new is_listable_top_level and window_title helpers Shim extensions: - coord_norm: desktop-scope support — denormalize_args falls back to screen basis when no window cache (desktop-scope click), normalize_result handles get_desktop_state, ingest_screen_size reads get_desktop_state's screen_width/height - serve.rs: rewrite_coord_desc re-injected at both tools/list sites Install scripts simplified to Rust-only (Swift backend retired upstream). Fix platform-linux cross-compilation: cfg-gate wayland::PORTAL_LIBEI_ENABLED reference in health_report.rs for non-linux hosts. * fix(cua-driver): distinguish desktop-scope from window cache miss in denormalize_args When screenshot_w == 0 (no cached window size), check whether the call has a pid/window_id target before falling back to screen-size basis. Window-scoped clicks with a cache miss now correctly leave coordinates as-is instead of mapping them against the screen dimensions. * fix(cua-driver): use screenshot pixels not logical points for desktop-scope coord basis ingest_screen_size was reading screen_width/height (logical display points from CGDisplayBounds) for get_desktop_state, but the model operates on the screenshot PNG whose dimensions are physical pixels. On Retina displays (2x) this caused every desktop-scope click to land at half the intended position. Read screenshot_width/height instead — the same basis normalize_result rewrites to 1000. Updated test to use realistic Retina values (screen 1920, screenshot 3840) to catch 1:1 masking. * fix(cua-driver): split screen cache into logical (move_cursor) and physical (desktop-scope) The single screen_cache was being written by both get_screen_size (logical points, e.g. 1920x1080) and get_desktop_state (physical pixels, e.g. 3840x2160). Since move_cursor operates in CGEvent screen points (logical) while desktop-scope clicks operate in screenshot pixels (physical), whichever tool ran last would corrupt the other's denormalization basis — causing 2x overshoot or 1/2 offset on Retina. Split into: - SCREEN_SIZE: logical points, written by get_screen_size, consumed by move_cursor (screen_basis=true) - DESKTOP_SCREENSHOT_SIZE: physical pixels, written by get_desktop_state, consumed by desktop-scope click fallback (screenshot_w==0, no pid) Added cross-contamination isolation tests. --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
194 lines
9.1 KiB
Bash
194 lines
9.1 KiB
Bash
# _install-common.sh — shared helpers for the .sh installers.
|
|
#
|
|
# Bash counterpart to CuaDriverInstall.psm1. Both files have to stay in
|
|
# lockstep on the kill / probe behaviour so a Mac/Linux dev fix matches
|
|
# what install.ps1 does on Windows. Function names mirror the
|
|
# PowerShell side:
|
|
#
|
|
# stop_cua_driver_daemons ↔ Stop-CuaDriverDaemons
|
|
# show_cua_driver_daemon_survivors ↔ Show-CuaDriverDaemonSurvivors
|
|
#
|
|
# This script is sourced (not exec'd) by the Rust install helpers:
|
|
#
|
|
# * _install-rust.sh (production Rust delegate)
|
|
# * _install-local-rust.sh (dev Rust installer)
|
|
#
|
|
# Loaders: on-disk first when run from a checked-out tree, else fetched
|
|
# from GitHub raw via `curl` (mirrors the irm | iex path that
|
|
# install.ps1 uses to import the .psm1 over the network). The inline
|
|
# loader is duplicated in each consumer because `source` doesn't have a
|
|
# bootstrap function the way PowerShell's `Import-Module` does — kept
|
|
# minimal so the duplication cost stays low.
|
|
#
|
|
# Keep this file narrow on purpose: every byte gets curl'd on every
|
|
# `curl ... | bash` install on non-macOS hosts (where install.sh
|
|
# auto-delegates to the Rust path) AND on dev installs. Things that DO
|
|
# belong here: kill / wait / probe helpers that multiple consumers
|
|
# need. Things that DON'T: anything only one script uses (leave it
|
|
# inline there).
|
|
#
|
|
# Style:
|
|
# * No `set -e` — sourced helpers shouldn't change the caller's shell
|
|
# options. Caller scripts are already `set -euo pipefail`.
|
|
# * Best-effort everywhere: every external command is suffixed with
|
|
# `|| true` (or wrapped in a subshell) so a kill failure never
|
|
# aborts the surrounding install.
|
|
# * Bash 3.2 compatible (macOS default). No associative arrays, no
|
|
# `[[ =~ ]]` patterns that need bash 4+.
|
|
|
|
# Best-effort kill of any running cua-driver daemons so the next
|
|
# `cua-driver` invocation starts the FRESH binary, not whatever's still
|
|
# in memory from the pre-upgrade install. Without this the previous
|
|
# daemon keeps running (and on macOS keeps holding TCC-attributed file
|
|
# handles) until the user logs out — which surfaces as "the bug I just
|
|
# fixed is still there" because the in-memory code is pre-fix.
|
|
#
|
|
# Layered escalation, in order of decreasing politeness:
|
|
# 1. macOS: `launchctl unload <plist>` on the Rust LaunchAgent
|
|
# (com.trycua.cua-driver-rs.plist). Unload
|
|
# is the documented way to stop a launchd-managed daemon — it
|
|
# also clears the KeepAlive flag so launchd doesn't immediately
|
|
# respawn the process we're about to kill. No-op (with stderr
|
|
# suppressed) when the plist isn't installed.
|
|
# Linux: `systemctl --user stop cua-driver-rs.service` for the
|
|
# install-local --autostart path. Same shape — politely stop the
|
|
# supervisor first so it doesn't restart the process.
|
|
# 2. `pkill -x qwen-cua-driver` as the backstop for processes that weren't
|
|
# launchd/systemd-supervised (e.g. a manual `qwen-cua-driver serve`
|
|
# from a dev shell, or `qwen-cua-driver mcp` spawned by an editor that
|
|
# isn't aware we're swapping the binary out from under it).
|
|
#
|
|
# Daemon-name coverage:
|
|
# This vendored fork's binary execs as `qwen-cua-driver` (a distinct
|
|
# product name so it coexists with any upstream `cua-driver` install —
|
|
# see build-app.sh `--identifier com.qwencode.cua-driver` and
|
|
# _install-rust.sh `BINARY_NAME="qwen-cua-driver"`). One `pkill -x
|
|
# qwen-cua-driver` targets exactly this fork's daemon. `cua-driver-uia`
|
|
# is a Windows-only helper (see
|
|
# packages/cua-driver/rust/crates/cua-driver-uia/) and never exists on
|
|
# macOS/Linux, so no pkill for it here.
|
|
#
|
|
# Returns: always 0. Caller threads this through unconditionally; the
|
|
# behaviour is "kill what we can, never block the install".
|
|
stop_cua_driver_daemons() {
|
|
printf '==> stopping any running cua-driver daemons before swap\n'
|
|
|
|
# Wrap the whole thing in a subshell so any unexpected non-zero exit
|
|
# (e.g. a `pkill` returning 1 when no process matches under a
|
|
# `set -e` caller — we shouldn't be `set -e` here, but defence in
|
|
# depth) never escapes.
|
|
(
|
|
case "$(uname -s 2>/dev/null || echo unknown)" in
|
|
Darwin)
|
|
# Both known LaunchAgent plists. `launchctl unload` is a
|
|
# no-op-with-warning when the plist doesn't exist; swallow
|
|
# stderr to keep the install log clean.
|
|
local plist
|
|
# Rust LaunchAgent plist.
|
|
plist="$HOME/Library/LaunchAgents/com.trycua.cua-driver-rs.plist"
|
|
if [ -f "$plist" ]; then
|
|
if [ -f "$plist" ]; then
|
|
launchctl unload "$plist" >/dev/null 2>&1 || true
|
|
fi
|
|
fi
|
|
;;
|
|
Linux)
|
|
# systemctl --user is the only supported supervisor on
|
|
# Linux today (install-local-rust.sh --autostart writes
|
|
# ~/.config/systemd/user/cua-driver-rs.service). `command
|
|
# -v` so we don't error on systemd-less hosts (musl
|
|
# containers, NixOS without user services, etc.).
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
systemctl --user stop cua-driver-rs.service >/dev/null 2>&1 || true
|
|
fi
|
|
;;
|
|
*)
|
|
# Other Unixes (FreeBSD, etc.) — no supervisor we know
|
|
# about, fall through to the pkill backstop.
|
|
;;
|
|
esac
|
|
|
|
# Best-effort: give the supervisor up to ~200ms to take the
|
|
# daemon down before we resort to pkill. Same wait as the
|
|
# PowerShell module after schtasks /End.
|
|
sleep 0.2 2>/dev/null || sleep 1
|
|
|
|
if command -v pkill >/dev/null 2>&1; then
|
|
# `-x` = exact match on process name so we don't kill a
|
|
# user's `qwen-cua-driver-foo` test harness or any script
|
|
# named "qwen-cua-driver-something". This vendored fork's
|
|
# binary execs as exactly `qwen-cua-driver` (distinct from
|
|
# the upstream `cua-driver` so the two coexist).
|
|
pkill -x qwen-cua-driver >/dev/null 2>&1 || true
|
|
fi
|
|
) || true
|
|
|
|
return 0
|
|
}
|
|
|
|
# Print a yellow warning if cua-driver processes are still running after
|
|
# stop_cua_driver_daemons did its best. On macOS/Linux this almost never
|
|
# fires for processes the current user owns — `pkill` succeeds against
|
|
# user-owned processes without elevation — so when it DOES fire it
|
|
# usually means:
|
|
#
|
|
# * Another user on the same host has their own cua-driver running
|
|
# (multi-user dev box). Their daemon is fine; ours just won't see
|
|
# the swap until they restart theirs.
|
|
# * The daemon was spawned with a custom signal handler that ignores
|
|
# SIGTERM (we don't ship such a handler — but a debugger session
|
|
# could install one).
|
|
# * Process is in uninterruptible state (D-state on Linux from a
|
|
# stuck syscall — rare for a userspace daemon).
|
|
#
|
|
# Mirrors Show-CuaDriverDaemonSurvivors on the PowerShell side, but the
|
|
# Unix-side mitigation is different: there's no clean User Account
|
|
# Control / High-IL escalation analogue, so the hint is just `sudo
|
|
# pkill` (root can reach other users' processes).
|
|
#
|
|
# Idempotent — safe to call even when stop_cua_driver_daemons did
|
|
# clean up everything. Prints nothing in the common case.
|
|
show_cua_driver_daemon_survivors() {
|
|
# `pgrep -x` matches the exact process name the same way `pkill -x`
|
|
# did above, so the survivor list is exactly what the kill couldn't
|
|
# reach (not random other qwen-cua-driver-* binaries).
|
|
if ! command -v pgrep >/dev/null 2>&1; then
|
|
# No pgrep, no way to check. Bail quietly — the install can
|
|
# still succeed; worst case the user notices a stale binary
|
|
# and reboots.
|
|
return 0
|
|
fi
|
|
|
|
local survivor_pids
|
|
survivor_pids=$(pgrep -x qwen-cua-driver 2>/dev/null || true)
|
|
if [ -z "$survivor_pids" ]; then
|
|
return 0
|
|
fi
|
|
|
|
# `tput` may not be available (CI, agent sandboxes without TERM);
|
|
# guard so we don't crash before printing the actual warning. The
|
|
# color is decoration — the text is the load-bearing part.
|
|
local yellow normal
|
|
yellow=$(tput setaf 3 2>/dev/null || true)
|
|
normal=$(tput sgr0 2>/dev/null || true)
|
|
|
|
# `wc -w` is portable across BSD and GNU; pgrep prints one pid per
|
|
# line so we'd get the same count from `wc -l`, but `wc -w` is
|
|
# robust to a missing trailing newline.
|
|
local count
|
|
count=$(printf '%s\n' "$survivor_pids" | wc -w | tr -d ' ')
|
|
local pid_csv
|
|
pid_csv=$(printf '%s\n' "$survivor_pids" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')
|
|
|
|
printf '%sNote: %s cua-driver process(es) still running after best-effort kill (pid: %s).%s\n' \
|
|
"$yellow" "$count" "$pid_csv" "$normal"
|
|
printf '%s They are probably owned by another user, or are ignoring SIGTERM.%s\n' \
|
|
"$yellow" "$normal"
|
|
printf '%s To force-kill (needs root if owned by another user):%s\n' \
|
|
"$yellow" "$normal"
|
|
printf '%s sudo pkill -9 -x qwen-cua-driver%s\n' \
|
|
"$yellow" "$normal"
|
|
printf '%s Or log out and back in. Until they exit, the OLD binary keeps running.%s\n' \
|
|
"$yellow" "$normal"
|
|
return 0
|
|
}
|