blob: fc01755e18494514ae9704558c78fd737e1e6ff4 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use maka_computer_use::{
Session,
protocol::{Command, InventoryOptions},
};
use tokio_util::sync::CancellationToken;
#[cfg(target_os = "linux")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore = "requires WSL, a matching Windows helper, and a disposable Windows form with MAKA_CUA_TEST_WINDOW"]
async fn wsl_uses_windows_targets_input_cursors_and_session_cleanup() {
use maka_capabilities::computer::Desktop;
use serde_json::{Value, json};
async fn call(session: &mut Session, id: &str, command: Value) -> Value {
session
.invoke(
serde_json::from_value(command).unwrap(),
id,
&CancellationToken::new(),
maka_agent_facts::language::Language::En,
)
.await
.unwrap()
}
assert!(
std::fs::read_to_string("/proc/sys/kernel/osrelease")
.unwrap()
.to_ascii_lowercase()
.contains("microsoft")
);
let window: u64 = std::env::var("MAKA_CUA_TEST_WINDOW")
.expect("disposable form window ID")
.parse()
.unwrap();
let driver = std::sync::Arc::new(tokio::sync::Mutex::new(maka_computer_use::Driver::default()));
let mut a = Session::new(driver.clone());
let mut b = Session::new(driver.clone());
a.configure(
Desktop::Auto,
vec![],
maka_agent_facts::language::Language::En,
)
.unwrap();
b.configure(
Desktop::Windows,
vec![],
maka_agent_facts::language::Language::En,
)
.unwrap();
assert_eq!(a.platform(), "windows");
assert!(
matches!(a.configure(Desktop::Local, vec![], maka_agent_facts::language::Language::En), Err(maka_agent_facts::tools::ToolError::Failed(message)) if message.contains("reset Cua"))
);
#[cfg(feature = "native-desktop")]
{
let mut local = Session::new(driver.clone());
local
.configure(
Desktop::Local,
vec![],
maka_agent_facts::language::Language::En,
)
.unwrap();
assert_eq!(local.platform(), "linux");
local.close().await.unwrap();
}
let app = call(
&mut a,
"wsl-a",
json!({"method":"getApp","target":{"windowId":window}}),
)
.await;
let state = app["state"].as_str().unwrap();
let line = state
.lines()
.find(|line| line.contains("WSL input"))
.unwrap_or_else(|| panic!("fixture field missing: {state}"));
let index: u64 = line
.trim_start_matches(|c: char| !c.is_ascii_digit())
.split(|c: char| !c.is_ascii_digit())
.next()
.unwrap()
.parse()
.unwrap();
call(&mut a, "wsl-a", json!({"method":"action","handle":app["handle"],"action":{"kind":"setValue","index":index,"value":"Maka WSL verified"}})).await;
let observed = call(
&mut a,
"wsl-a",
json!({"method":"observe","handle":app["handle"],"kind":"both","options":{}}),
)
.await;
assert!(
observed["state"]
.as_str()
.unwrap()
.contains("Maka WSL verified")
);
assert!(observed["image"]["data"].as_str().unwrap().len() > 100);
let foreign = b
.invoke(
serde_json::from_value(
json!({"method":"observe","handle":app["handle"],"kind":"ax","options":{}}),
)
.unwrap(),
"wsl-b",
&CancellationToken::new(),
maka_agent_facts::language::Language::En,
)
.await;
assert!(
matches!(foreign, Err(maka_agent_facts::tools::ToolError::Failed(message)) if message.contains("stale_target"))
);
let other = call(
&mut b,
"wsl-b",
json!({"method":"getApp","target":{"windowId":window}}),
)
.await;
for (session, id, handle, label, color, point) in [
(&mut a, "wsl-a", &app["handle"], "WSL A", "blue", [20, 20]),
(
&mut b,
"wsl-b",
&other["handle"],
"WSL B",
"mint",
[120, 20],
),
] {
call(session, id, json!({"method":"configureCursor","options":{"label":label,"color":color,"reducedMotion":true}})).await;
call(
session,
id,
json!({"method":"observe","handle":handle,"kind":"screenshot","options":{}}),
)
.await;
call(session, id, json!({"method":"action","handle":handle,"action":{"kind":"moveCursor","target":point}})).await;
}
let cursor_a = call(
&mut a,
"wsl-a",
json!({"method":"cursorState","options":{}}),
)
.await;
let cursor_b = call(
&mut b,
"wsl-b",
json!({"method":"cursorState","options":{}}),
)
.await;
assert_eq!(cursor_a["native"]["status"], "ready");
assert_eq!(cursor_b["native"]["status"], "ready");
assert_eq!(
cursor_a["native"]["rendererPid"],
cursor_b["native"]["rendererPid"]
);
assert_ne!(cursor_a["settings"]["id"], cursor_b["settings"]["id"]);
a.close().await.unwrap();
assert_eq!(
call(
&mut b,
"wsl-b",
json!({"method":"cursorState","options":{}})
)
.await["native"]["visible"],
true
);
let result = call(
&mut b,
"wsl-b",
json!({"method":"observe","handle":other["handle"],"kind":"ax","options":{}}),
)
.await;
assert!(
result["state"]
.as_str()
.unwrap()
.contains("Maka WSL verified")
);
b.close().await.unwrap();
driver.lock().await.close().await.unwrap();
println!(
"WSL_WINDOWS_VERIFIED renderer_pid={}",
cursor_b["native"]["rendererPid"]
);
}
#[tokio::test]
async fn closed_or_cancelled_session_cannot_start_native_work() {
let driver = std::sync::Arc::new(tokio::sync::Mutex::new(maka_computer_use::Driver::default()));
let mut run = Session::new(driver.clone());
let stop = CancellationToken::new();
stop.cancel();
assert!(
matches!(run.prepare(&stop, maka_agent_facts::language::Language::En).await, Err(maka_agent_facts::tools::ToolError::Failed(message)) if message == "Computer Use preparation cancelled")
);
assert!(
run.invoke(
Command::ListApps {
options: InventoryOptions::default()
},
"cancelled",
&stop,
maka_agent_facts::language::Language::En
)
.await
.unwrap_err()
.to_string()
.contains("cancelled before dispatch")
);
run.close().await.unwrap();
assert!(
matches!(run.prepare(&CancellationToken::new(), maka_agent_facts::language::Language::En).await, Err(maka_agent_facts::tools::ToolError::Failed(message)) if message == "Computer Use Session is closed")
);
assert!(
run.invoke(
Command::ListApps {
options: InventoryOptions::default()
},
"closed",
&CancellationToken::new(),
maka_agent_facts::language::Language::En
)
.await
.unwrap_err()
.to_string()
.contains("Session is closed")
);
run.close().await.unwrap();
}
#[cfg(feature = "native-desktop")]
#[tokio::test]
#[ignore = "requires an interactive desktop; lists applications without sending input"]
async fn native_application_observation_and_shutdown() {
let driver = std::sync::Arc::new(tokio::sync::Mutex::new(maka_computer_use::Driver::default()));
let mut run = Session::new(driver.clone());
let result = run
.invoke(
Command::ListApps {
options: InventoryOptions::default(),
},
"maka-native-smoke",
&CancellationToken::new(),
maka_agent_facts::language::Language::En,
)
.await;
let mut other = Session::new(driver.clone());
let second = other
.invoke(
Command::ListApps {
options: InventoryOptions::default(),
},
"maka-native-second",
&CancellationToken::new(),
maka_agent_facts::language::Language::En,
)
.await
.unwrap();
assert!(second.is_array());
run.close().await.unwrap();
let still_live = other
.invoke(
Command::ListWindows {
options: InventoryOptions::default(),
},
"maka-native-second",
&CancellationToken::new(),
maka_agent_facts::language::Language::En,
)
.await
.unwrap();
assert!(still_live.is_array());
other.close().await.unwrap();
driver.lock().await.close().await.unwrap();
let result = result.unwrap();
assert!(result.is_array());
}
#[cfg(all(feature = "native-desktop", target_os = "macos"))]
#[tokio::test]
#[ignore = "requires a locked macOS desktop; verifies refusal without launching or controlling an app"]
async fn locked_desktop_refuses_target_binding_before_app_resolution() {
let driver = std::sync::Arc::new(tokio::sync::Mutex::new(maka_computer_use::Driver::default()));
let mut session = Session::new(driver.clone());
let error = session
.invoke(
Command::GetApp {
target: maka_computer_use::protocol::AppReference::Window {
window_id: u64::MAX,
},
},
"locked-fixture",
&CancellationToken::new(),
maka_agent_facts::language::Language::En,
)
.await
.unwrap_err();
assert!(error.to_string().contains("desktop_locked"), "{error}");
session.close().await.unwrap();
driver.lock().await.close().await.unwrap();
}