Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)." },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
37 changes: 34 additions & 3 deletions libs/cua-driver/rust/crates/platform-macos/src/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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." },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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." },
Expand Down
Loading