fix(cua-driver-rs)(windows): per-session overlay windows, visible distinct cursors, parallel-transport discoverability - #1804
Conversation
…on HWND
A brand-new session cursor stays invisible until the first sibling tool
runs because move_cursor doesn't trigger PinAbove. The overlay HWND lands
at an unpinned z-position — usually behind the target window — so the
visible cursor is hidden under the app even though the daemon reports
the move succeeded.
Repro: start_session("demo") + move_cursor(x, y) → invisible. Add a
click(...) first → cursor appears (click's pin_overlay_above brings the
overlay to z+1 of the target HWND).
Sibling tools (click, type_text, drag, …) explicitly pin against their
declared target window. move_cursor only carries (x, y), so this patch
derives the target via WindowFromPoint(x, y) at the destination and
feeds it to the existing pin_overlay_above helper.
The overlay is WS_EX_TRANSPARENT (overlay.rs:482), so WindowFromPoint
walks past it and returns the app behind. Over empty desktop the
desktop HWND comes back — pin_overlay_above resolves to GA_ROOT and
PinAbove no-ops harmlessly.
macOS handles the same bug differently: its move_cursor routes through
`animate_cursor_to`, which seeds the off-screen sentinel and lets the
render-loop ZOrderEnforcer handle pinning per-tick. The Windows overlay
uses explicit per-tool pins instead — hence the per-tool fix here. See
project_agent_cursor_zorder.md for cross-platform context.
Verified live: fresh session + move_cursor → cursor visible at the
destination without any prior click on the target window.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
📝 WalkthroughWalkthroughMoveCursorTool::invoke now pins the agent-cursor overlay above the window at the destination coordinates. When cursor placement occurs, the code uses WindowFromPoint to find the HWND and calls pin_overlay_above to ensure z-visibility immediately without waiting for other tools. ChangesCursor overlay pinning
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The Streamable-HTTP MCP transport (one connection per agent = true parallelism) was enabled only via an env var and invisible to agents. - serve --http-port <port>: first-class flag (env var still honored, flag wins), documented in --help. - cua-driver status reports 'mcp-http: listening on <url>' / disabled, re-probing the port so a stale state file after a crash reads as off. - The daemon self-advertises the LIVE endpoint URL in its MCP initialize instructions only when the listener actually bound. - Instructions + SKILL.md now spell out the concurrency contract: ONE session = one connection = sequential (FIFO); parallelism is ACROSS sessions, never within one (per-session cursor + recording assume an ordered stream). SKILL.md gains a worked curl example and the same-daemon element-cache caveat. - Fixed stale 'snaps on first action' cursor wording (instructions + --help) to match the seed-and-glide behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three visibility/color fixes for per-session cursors: - First action glides in instead of snapping: overlay_glide_to dropped its ClickPulse-snap shortcut and always defers to animate_cursor_to (seed_start_if_sentinel gives the seeded glide), matching macOS. - cursor_color now actually renders: set_agent_cursor_motion stored it in the registry but never reached the painter (which reads only gradient_override/palette). It now emits SetGradient with a solid one-stop gradient + lightened bloom. - Collision-free auto palettes: ids sharing a numeric suffix (e.g. mil-005253/rom-005253) all hashed to the SAME palette via stable_index's suffix rule, so concurrent cursors rendered one color. Palette::for_instance_distinct linear-probes past palettes held by live cursors (hash stays the preferred pick), and PALETTE_DATA gains six hue-gap fillers (emerald, gold, indigo, tangerine, hot_pink, teal). Regression-tested. - Display capture uses SRCCOPY|CAPTUREBLT so layered windows (the agent cursor overlay) appear in full-screen screenshots. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…z-order The overlay was ONE layered window compositing every session cursor, pinned z+1 of only the LAST-ACTIVE cursor's target — so when cursor B acted, the shared window re-pinned to B's z-band and cursors over other windows lost z+1 of their own target (occluded by their own window). Each cursor key now owns its OWN layered window: - RenderState gains hwnd_isize + per-key WinZOrderEnforcer + pixmap; RenderMap.last_active and the shared Z_ORDER band are gone. - Single STA thread + single WM_TIMER still drives everything: lazily creates a key's window on its first observed tick, ticks, paints, and reasserts each window z+1 of ITS OWN pinned_wid. - Each cursor paints into a tiny 128x128 box that FOLLOWS it (UpdateLayeredWindow pt_dst), not a full-virtual-screen RGBA buffer (~14MB/cursor at 2560x1440) — the cursor's drawn extent is fixed (arrow <=14px, bloom +-22px, ring <=~26px). - session_end DestroyWindow's that key's window; supervisor WM_DESTROY tears down every per-key window (no HWND leak when a session never ends); a disabled cursor hides its window once (no frozen ghost frame — found in adversarial review). - WM_DISPLAYCHANGE re-captures the virtual screen and resizes all windows: the VM's resolution flip (1512x949 <-> 2560x1440) left the overlay covering a stale region, so cursors outside it never painted. - Per-key palette assignment threads palettes_in_use into render_state_for_key for the collision-free picker. Runtime-verified: per-session 128x128 overlay windows tracking their cursors independently, end_session destroying exactly its window, and four sessions filling four windows simultaneously with stable z. macOS parity tracked in #1807; long-lived-daemon degradation in #1806. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Closing this broad, conflicting prototype because its concerns have since evolved through narrower work: HTTP transport shipped in #1798; cursor badge and presentation work continued in #2616, #2677, and #2842; and the macOS overlay z-order question is tracked in #1800. Any still-desired Windows per-window overlay semantics should be proposed as a focused issue against current main rather than reviving this multi-topic PR. |
Summary
Four related improvements to the Rust driver's agent-cursor + parallel-agent story, developed and runtime-verified live on Windows:
1. Per-session overlay windows — stable multi-cursor z-order
The overlay was a single layered window compositing every session cursor, pinned z+1 of only the last-active cursor's target — any action by one cursor dragged all others out of their own window's z-band. Each cursor key now owns its own small layered window (lazy-created, destroyed on
session_end, supervisor cleans up all on shutdown), each pinned z+1 of its own target, painted into a 128×128 box that follows the cursor instead of a ~14MB full-screen buffer per cursor. Also:WM_DISPLAYCHANGEre-captures the virtual screen (the VM resolution flip 1512×949↔2560×1440 previously left cursors rendering outside a stale overlay), and a disabled cursor hides its window instead of freezing a ghost frame. macOS parity: #1807. Long-lived-daemon degradation: #1806.2. Visible, distinctly-colored cursors
set_agent_cursor_motion {cursor_color}now actually renders (it was stored but never reached the painter) viaSetGradient.stable_indexsuffix rule) → identical colors;for_instance_distinctprobes past in-use palettes, and the palette grows by six hue-gap colors.SRCCOPY|CAPTUREBLTso the layered overlay appears in full-screen screenshots.3. Parallel HTTP transport discoverability
serve --http-port <port>first-class flag (env var still honored).cua-driver statusreports the livemcp-httpendpoint (self-correcting via port probe).initializeinstructions only when the listener genuinely bound.4. Docs: the concurrency contract
SKILL.md + MCP instructions now state it explicitly: one session = one connection = sequential (FIFO); parallelism is across sessions, never within one (per-session cursor + recording assume an ordered stream) — with a worked
curlexample and the same-daemon element-cache caveat.Test plan
cargo test: cursor-overlay 7/7, cua-driver-core 51/51, platform-windows 49/49, cua-driver bin 62/62; release build clean.end_sessiondestroys exactly its window; four concurrent sessions filled four windows simultaneously (staggered, sequential-within-session) with distinct colors and stable z; native recording captures it end-to-end.🤖 Generated with Claude Code