diff --git a/libs/cua-driver/rust/crates/platform-macos/src/tools/click.rs b/libs/cua-driver/rust/crates/platform-macos/src/tools/click.rs index b40532d629..92bf1977b7 100644 --- a/libs/cua-driver/rust/crates/platform-macos/src/tools/click.rs +++ b/libs/cua-driver/rust/crates/platform-macos/src/tools/click.rs @@ -133,6 +133,7 @@ fn def() -> &'static ToolDef { // cua_driver_core::tool_schema.) "required": [], "properties": { + "observe_window_changes": { "type": "boolean", "default": true, "description": "Set false to skip the post-action window-change poll (up to ~1s) — for deterministic callers (macro replay, harnesses) that already know what the next action expects. The result then omits new-window/foreground-change notes." }, "session": { "type": "string", "description": "Optional session id: declares/uses the agent cursor and per-session state for this run. The same id works over MCP, the CLI, or the raw socket, and follows the run across apps/windows. Omit to run cursor-less." }, "pid": { "type": "integer", "description": "Target process ID." }, "window_id": { "type": "integer", "description": "Target window ID. Required for element_index. Optional when element_token is supplied (the token carries it)." }, diff --git a/libs/cua-driver/rust/crates/platform-macos/src/tools/drag.rs b/libs/cua-driver/rust/crates/platform-macos/src/tools/drag.rs index 4d7b690e09..1208f0f433 100644 --- a/libs/cua-driver/rust/crates/platform-macos/src/tools/drag.rs +++ b/libs/cua-driver/rust/crates/platform-macos/src/tools/drag.rs @@ -56,6 +56,7 @@ fn def() -> &'static ToolDef { "type": "object", "required": ["from_x", "from_y", "to_x", "to_y"], "properties": { + "observe_window_changes": { "type": "boolean", "default": true, "description": "Set false to skip the post-action window-change poll (up to ~1s) — for deterministic callers (macro replay, harnesses) that already know what the next action expects. The result then omits new-window/foreground-change notes." }, "session": { "type": "string", "description": "Optional session id: declares/uses the agent cursor and per-session state for this run. The same id works over MCP, the CLI, or the raw socket, and follows the run across apps/windows. Omit to run cursor-less." }, "pid": { "type": "integer", "description": "Target process ID." }, "window_id": { diff --git a/libs/cua-driver/rust/crates/platform-macos/src/tools/hotkey.rs b/libs/cua-driver/rust/crates/platform-macos/src/tools/hotkey.rs index 1f74f3c63a..171a75256b 100644 --- a/libs/cua-driver/rust/crates/platform-macos/src/tools/hotkey.rs +++ b/libs/cua-driver/rust/crates/platform-macos/src/tools/hotkey.rs @@ -59,6 +59,7 @@ fn def() -> &'static ToolDef { "type": "object", "required": ["keys"], "properties": { + "observe_window_changes": { "type": "boolean", "default": true, "description": "Set false to skip the post-action window-change poll (up to ~1s) — for deterministic callers (macro replay, harnesses) that already know what the next action expects. The result then omits new-window/foreground-change notes." }, "session": { "type": "string", "description": "Optional session id: declares/uses the agent cursor and per-session state for this run. The same id works over MCP, the CLI, or the raw socket, and follows the run across apps/windows. Omit to run cursor-less." }, "pid": { "type": "integer", "description": "Target process ID." }, "keys": { diff --git a/libs/cua-driver/rust/crates/platform-macos/src/tools/mod.rs b/libs/cua-driver/rust/crates/platform-macos/src/tools/mod.rs index f5c5ca2277..eb68c4c9d0 100644 --- a/libs/cua-driver/rust/crates/platform-macos/src/tools/mod.rs +++ b/libs/cua-driver/rust/crates/platform-macos/src/tools/mod.rs @@ -109,10 +109,22 @@ pub(crate) async fn finish_window_observation( snapshot: crate::window_change_detector::Snapshot, args: &serde_json::Value, ) -> crate::window_change_detector::Changes { - if args - .get("_skip_window_change_detection") + // Public per-call opt-out: deterministic callers (macro replayers, test + // harnesses) know what the next action expects and don't need the + // up-to-1s post-action window-change poll — for them it is pure latency + // on every input action. Additive and backwards-compatible: omitted or + // true keeps today's behavior. Distinct from the transport-reserved + // underscore flag below, which public callers cannot send + // (sanitize_reserved_args strips it). + let observe = args + .get("observe_window_changes") .and_then(serde_json::Value::as_bool) - .unwrap_or(false) + .unwrap_or(true); + if !observe + || args + .get("_skip_window_change_detection") + .and_then(serde_json::Value::as_bool) + .unwrap_or(false) { drop(snapshot); crate::window_change_detector::Changes::no_change() @@ -135,6 +147,25 @@ mod interactive_observation_tests { .await; assert!(!changes.needs_restore()); } + + /// The public opt-out must skip the poll exactly like the internal flag: + /// a caller passing observe_window_changes:false gets an immediate + /// no-change result instead of the up-to-1s detect poll. + #[tokio::test] + async fn public_observe_window_changes_false_skips_polling() { + let snapshot = crate::window_change_detector::WindowChangeDetector::snapshot(None); + let started = std::time::Instant::now(); + let changes = finish_window_observation( + snapshot, + &serde_json::json!({"observe_window_changes": false}), + ) + .await; + assert!(!changes.needs_restore()); + assert!( + started.elapsed() < std::time::Duration::from_millis(500), + "opt-out must not run the detect poll" + ); + } } /// px-focus for the keyboard family (type_text / press_key / hotkey): focus the diff --git a/libs/cua-driver/rust/crates/platform-macos/src/tools/press_key.rs b/libs/cua-driver/rust/crates/platform-macos/src/tools/press_key.rs index 6649bdf921..7621b3d91f 100644 --- a/libs/cua-driver/rust/crates/platform-macos/src/tools/press_key.rs +++ b/libs/cua-driver/rust/crates/platform-macos/src/tools/press_key.rs @@ -48,6 +48,7 @@ fn def() -> &'static ToolDef { "type": "object", "required": ["key"], "properties": { + "observe_window_changes": { "type": "boolean", "default": true, "description": "Set false to skip the post-action window-change poll (up to ~1s) — for deterministic callers (macro replay, harnesses) that already know what the next action expects. The result then omits new-window/foreground-change notes." }, "session": { "type": "string", "description": "Optional session id: declares/uses the agent cursor and per-session state for this run. The same id works over MCP, the CLI, or the raw socket, and follows the run across apps/windows. Omit to run cursor-less." }, "pid": { "type": "integer" }, "key": { "type": "string", "description": "Key name: return, tab, escape, up, down, etc." }, diff --git a/libs/cua-driver/rust/crates/platform-macos/src/tools/scroll.rs b/libs/cua-driver/rust/crates/platform-macos/src/tools/scroll.rs index 986dbed249..7ec7fb40ec 100644 --- a/libs/cua-driver/rust/crates/platform-macos/src/tools/scroll.rs +++ b/libs/cua-driver/rust/crates/platform-macos/src/tools/scroll.rs @@ -71,6 +71,7 @@ fn def() -> &'static ToolDef { // schema — keeps the contract consistent across platforms. "required": ["direction"], "properties": { + "observe_window_changes": { "type": "boolean", "default": true, "description": "Set false to skip the post-action window-change poll (up to ~1s) — for deterministic callers (macro replay, harnesses) that already know what the next action expects. The result then omits new-window/foreground-change notes." }, "session": { "type": "string", "description": "Optional session id: declares/uses the agent cursor and per-session state for this run. The same id works over MCP, the CLI, or the raw socket, and follows the run across apps/windows. Omit to run cursor-less." }, "pid": { "type": "integer" }, "direction": { diff --git a/libs/cua-driver/rust/crates/platform-macos/src/tools/type_text.rs b/libs/cua-driver/rust/crates/platform-macos/src/tools/type_text.rs index d74f5df922..1221ebe040 100644 --- a/libs/cua-driver/rust/crates/platform-macos/src/tools/type_text.rs +++ b/libs/cua-driver/rust/crates/platform-macos/src/tools/type_text.rs @@ -86,6 +86,7 @@ fn def() -> &'static ToolDef { "type": "object", "required": ["text"], "properties": { + "observe_window_changes": { "type": "boolean", "default": true, "description": "Set false to skip the post-action window-change poll (up to ~1s) — for deterministic callers (macro replay, harnesses) that already know what the next action expects. The result then omits new-window/foreground-change notes." }, "session": { "type": "string", "description": "Optional session id: declares/uses the agent cursor and per-session state for this run. The same id works over MCP, the CLI, or the raw socket, and follows the run across apps/windows. Omit to run cursor-less." }, "pid": { "type": "integer", "description": "Target process ID." }, "text": { "type": "string", "description": "Text to insert at the target's cursor." },