mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 07:04:48 +00:00
Fix desktop scroll, timeout cleanup, and OpenAPI codes
This commit is contained in:
parent
406f55dc52
commit
2887e8694b
3 changed files with 121 additions and 25 deletions
|
|
@ -745,7 +745,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"503": {
|
"502": {
|
||||||
"description": "Desktop runtime health or input failed",
|
"description": "Desktop runtime health or input failed",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
|
|
@ -807,7 +807,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"503": {
|
"502": {
|
||||||
"description": "Desktop runtime health or input failed",
|
"description": "Desktop runtime health or input failed",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
|
|
@ -869,7 +869,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"503": {
|
"502": {
|
||||||
"description": "Desktop runtime health or input failed",
|
"description": "Desktop runtime health or input failed",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
|
|
@ -931,7 +931,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"503": {
|
"502": {
|
||||||
"description": "Desktop runtime health or input failed",
|
"description": "Desktop runtime health or input failed",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
|
|
@ -993,7 +993,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"503": {
|
"502": {
|
||||||
"description": "Desktop runtime health or input failed",
|
"description": "Desktop runtime health or input failed",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
|
|
@ -1035,7 +1035,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"503": {
|
"502": {
|
||||||
"description": "Desktop runtime health or input check failed",
|
"description": "Desktop runtime health or input check failed",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
|
|
@ -1097,7 +1097,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"503": {
|
"502": {
|
||||||
"description": "Desktop runtime health or input failed",
|
"description": "Desktop runtime health or input failed",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
|
|
@ -1132,7 +1132,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"503": {
|
"502": {
|
||||||
"description": "Desktop runtime health or screenshot capture failed",
|
"description": "Desktop runtime health or screenshot capture failed",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
|
|
@ -1221,7 +1221,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"503": {
|
"502": {
|
||||||
"description": "Desktop runtime health or screenshot capture failed",
|
"description": "Desktop runtime health or screenshot capture failed",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
|
|
|
||||||
|
|
@ -386,8 +386,8 @@ impl DesktopRuntime {
|
||||||
request.y.to_string(),
|
request.y.to_string(),
|
||||||
];
|
];
|
||||||
|
|
||||||
append_scroll_clicks(&mut args, delta_y, 4, 5);
|
append_scroll_clicks(&mut args, delta_y, 5, 4);
|
||||||
append_scroll_clicks(&mut args, delta_x, 6, 7);
|
append_scroll_clicks(&mut args, delta_x, 7, 6);
|
||||||
|
|
||||||
self.run_input_command_locked(&state, &ready, args).await?;
|
self.run_input_command_locked(&state, &ready, args).await?;
|
||||||
self.mouse_position_locked(&state, &ready).await
|
self.mouse_position_locked(&state, &ready).await
|
||||||
|
|
@ -1192,6 +1192,8 @@ async fn run_command_output(
|
||||||
environment: &HashMap<String, String>,
|
environment: &HashMap<String, String>,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
) -> Result<Output, String> {
|
) -> Result<Output, String> {
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
let mut child = Command::new(command);
|
let mut child = Command::new(command);
|
||||||
child.args(args);
|
child.args(args);
|
||||||
child.envs(environment);
|
child.envs(environment);
|
||||||
|
|
@ -1199,11 +1201,51 @@ async fn run_command_output(
|
||||||
child.stdout(Stdio::piped());
|
child.stdout(Stdio::piped());
|
||||||
child.stderr(Stdio::piped());
|
child.stderr(Stdio::piped());
|
||||||
|
|
||||||
let output = tokio::time::timeout(timeout, child.output())
|
let mut child = child.spawn().map_err(|err| err.to_string())?;
|
||||||
|
let stdout = child
|
||||||
|
.stdout
|
||||||
|
.take()
|
||||||
|
.ok_or_else(|| "failed to capture child stdout".to_string())?;
|
||||||
|
let stderr = child
|
||||||
|
.stderr
|
||||||
|
.take()
|
||||||
|
.ok_or_else(|| "failed to capture child stderr".to_string())?;
|
||||||
|
|
||||||
|
let stdout_task = tokio::spawn(async move {
|
||||||
|
let mut stdout = stdout;
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
stdout.read_to_end(&mut bytes).await.map(|_| bytes)
|
||||||
|
});
|
||||||
|
let stderr_task = tokio::spawn(async move {
|
||||||
|
let mut stderr = stderr;
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
stderr.read_to_end(&mut bytes).await.map(|_| bytes)
|
||||||
|
});
|
||||||
|
|
||||||
|
let status = match tokio::time::timeout(timeout, child.wait()).await {
|
||||||
|
Ok(result) => result.map_err(|err| err.to_string())?,
|
||||||
|
Err(_) => {
|
||||||
|
terminate_child(&mut child).await?;
|
||||||
|
let _ = stdout_task.await;
|
||||||
|
let _ = stderr_task.await;
|
||||||
|
return Err(format!("command timed out after {}s", timeout.as_secs()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdout = stdout_task
|
||||||
.await
|
.await
|
||||||
.map_err(|_| format!("command timed out after {}s", timeout.as_secs()))?
|
.map_err(|err| err.to_string())?
|
||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
Ok(output)
|
let stderr = stderr_task
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
Ok(Output {
|
||||||
|
status,
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn terminate_child(child: &mut Child) -> Result<(), String> {
|
async fn terminate_child(child: &mut Child) -> Result<(), String> {
|
||||||
|
|
@ -1349,11 +1391,20 @@ fn mouse_button_code(button: DesktopMouseButton) -> u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append_scroll_clicks(args: &mut Vec<String>, delta: i32, up_button: u8, down_button: u8) {
|
fn append_scroll_clicks(
|
||||||
|
args: &mut Vec<String>,
|
||||||
|
delta: i32,
|
||||||
|
positive_button: u8,
|
||||||
|
negative_button: u8,
|
||||||
|
) {
|
||||||
if delta == 0 {
|
if delta == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let button = if delta > 0 { up_button } else { down_button };
|
let button = if delta > 0 {
|
||||||
|
positive_button
|
||||||
|
} else {
|
||||||
|
negative_button
|
||||||
|
};
|
||||||
let repeat = delta.unsigned_abs();
|
let repeat = delta.unsigned_abs();
|
||||||
args.push("click".to_string());
|
args.push("click".to_string());
|
||||||
if repeat > 1 {
|
if repeat > 1 {
|
||||||
|
|
@ -1402,4 +1453,49 @@ mod tests {
|
||||||
let args = press_key_args("--help".to_string());
|
let args = press_key_args("--help".to_string());
|
||||||
assert_eq!(args, vec!["key", "--", "--help"]);
|
assert_eq!(args, vec!["key", "--", "--help"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn append_scroll_clicks_uses_positive_direction_buttons() {
|
||||||
|
let mut args = Vec::new();
|
||||||
|
append_scroll_clicks(&mut args, 2, 5, 4);
|
||||||
|
append_scroll_clicks(&mut args, -3, 7, 6);
|
||||||
|
assert_eq!(
|
||||||
|
args,
|
||||||
|
vec!["click", "--repeat", "2", "5", "click", "--repeat", "3", "6"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn run_command_output_kills_child_on_timeout() {
|
||||||
|
let pid_file = std::env::temp_dir().join(format!(
|
||||||
|
"sandbox-agent-desktop-runtime-timeout-{}.pid",
|
||||||
|
std::process::id()
|
||||||
|
));
|
||||||
|
let _ = std::fs::remove_file(&pid_file);
|
||||||
|
let command = format!("echo $$ > {}; exec sleep 30", pid_file.display());
|
||||||
|
let args = vec!["-c".to_string(), command];
|
||||||
|
|
||||||
|
let error = run_command_output("sh", &args, &HashMap::new(), Duration::from_millis(200))
|
||||||
|
.await
|
||||||
|
.expect_err("command should time out");
|
||||||
|
assert!(error.contains("timed out"));
|
||||||
|
|
||||||
|
let pid = std::fs::read_to_string(&pid_file)
|
||||||
|
.expect("pid file should exist")
|
||||||
|
.trim()
|
||||||
|
.parse::<u32>()
|
||||||
|
.expect("pid should parse");
|
||||||
|
|
||||||
|
for _ in 0..20 {
|
||||||
|
if !process_exists(pid) {
|
||||||
|
let _ = std::fs::remove_file(&pid_file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = std::fs::remove_file(&pid_file);
|
||||||
|
panic!("timed out child process {pid} still exists after timeout cleanup");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -624,7 +624,7 @@ async fn post_v1_desktop_stop(
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Desktop screenshot as PNG bytes"),
|
(status = 200, description = "Desktop screenshot as PNG bytes"),
|
||||||
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
||||||
(status = 503, description = "Desktop runtime health or screenshot capture failed", body = ProblemDetails)
|
(status = 502, description = "Desktop runtime health or screenshot capture failed", body = ProblemDetails)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
async fn get_v1_desktop_screenshot(
|
async fn get_v1_desktop_screenshot(
|
||||||
|
|
@ -652,7 +652,7 @@ async fn get_v1_desktop_screenshot(
|
||||||
(status = 200, description = "Desktop screenshot region as PNG bytes"),
|
(status = 200, description = "Desktop screenshot region as PNG bytes"),
|
||||||
(status = 400, description = "Invalid screenshot region", body = ProblemDetails),
|
(status = 400, description = "Invalid screenshot region", body = ProblemDetails),
|
||||||
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
||||||
(status = 503, description = "Desktop runtime health or screenshot capture failed", body = ProblemDetails)
|
(status = 502, description = "Desktop runtime health or screenshot capture failed", body = ProblemDetails)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
async fn get_v1_desktop_screenshot_region(
|
async fn get_v1_desktop_screenshot_region(
|
||||||
|
|
@ -673,7 +673,7 @@ async fn get_v1_desktop_screenshot_region(
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Desktop mouse position", body = DesktopMousePositionResponse),
|
(status = 200, description = "Desktop mouse position", body = DesktopMousePositionResponse),
|
||||||
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
||||||
(status = 503, description = "Desktop runtime health or input check failed", body = ProblemDetails)
|
(status = 502, description = "Desktop runtime health or input check failed", body = ProblemDetails)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
async fn get_v1_desktop_mouse_position(
|
async fn get_v1_desktop_mouse_position(
|
||||||
|
|
@ -696,7 +696,7 @@ async fn get_v1_desktop_mouse_position(
|
||||||
(status = 200, description = "Desktop mouse position after move", body = DesktopMousePositionResponse),
|
(status = 200, description = "Desktop mouse position after move", body = DesktopMousePositionResponse),
|
||||||
(status = 400, description = "Invalid mouse move request", body = ProblemDetails),
|
(status = 400, description = "Invalid mouse move request", body = ProblemDetails),
|
||||||
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
||||||
(status = 503, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
(status = 502, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
async fn post_v1_desktop_mouse_move(
|
async fn post_v1_desktop_mouse_move(
|
||||||
|
|
@ -720,7 +720,7 @@ async fn post_v1_desktop_mouse_move(
|
||||||
(status = 200, description = "Desktop mouse position after click", body = DesktopMousePositionResponse),
|
(status = 200, description = "Desktop mouse position after click", body = DesktopMousePositionResponse),
|
||||||
(status = 400, description = "Invalid mouse click request", body = ProblemDetails),
|
(status = 400, description = "Invalid mouse click request", body = ProblemDetails),
|
||||||
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
||||||
(status = 503, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
(status = 502, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
async fn post_v1_desktop_mouse_click(
|
async fn post_v1_desktop_mouse_click(
|
||||||
|
|
@ -744,7 +744,7 @@ async fn post_v1_desktop_mouse_click(
|
||||||
(status = 200, description = "Desktop mouse position after drag", body = DesktopMousePositionResponse),
|
(status = 200, description = "Desktop mouse position after drag", body = DesktopMousePositionResponse),
|
||||||
(status = 400, description = "Invalid mouse drag request", body = ProblemDetails),
|
(status = 400, description = "Invalid mouse drag request", body = ProblemDetails),
|
||||||
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
||||||
(status = 503, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
(status = 502, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
async fn post_v1_desktop_mouse_drag(
|
async fn post_v1_desktop_mouse_drag(
|
||||||
|
|
@ -768,7 +768,7 @@ async fn post_v1_desktop_mouse_drag(
|
||||||
(status = 200, description = "Desktop mouse position after scroll", body = DesktopMousePositionResponse),
|
(status = 200, description = "Desktop mouse position after scroll", body = DesktopMousePositionResponse),
|
||||||
(status = 400, description = "Invalid mouse scroll request", body = ProblemDetails),
|
(status = 400, description = "Invalid mouse scroll request", body = ProblemDetails),
|
||||||
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
||||||
(status = 503, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
(status = 502, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
async fn post_v1_desktop_mouse_scroll(
|
async fn post_v1_desktop_mouse_scroll(
|
||||||
|
|
@ -792,7 +792,7 @@ async fn post_v1_desktop_mouse_scroll(
|
||||||
(status = 200, description = "Desktop keyboard action result", body = DesktopActionResponse),
|
(status = 200, description = "Desktop keyboard action result", body = DesktopActionResponse),
|
||||||
(status = 400, description = "Invalid keyboard type request", body = ProblemDetails),
|
(status = 400, description = "Invalid keyboard type request", body = ProblemDetails),
|
||||||
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
||||||
(status = 503, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
(status = 502, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
async fn post_v1_desktop_keyboard_type(
|
async fn post_v1_desktop_keyboard_type(
|
||||||
|
|
@ -816,7 +816,7 @@ async fn post_v1_desktop_keyboard_type(
|
||||||
(status = 200, description = "Desktop keyboard action result", body = DesktopActionResponse),
|
(status = 200, description = "Desktop keyboard action result", body = DesktopActionResponse),
|
||||||
(status = 400, description = "Invalid keyboard press request", body = ProblemDetails),
|
(status = 400, description = "Invalid keyboard press request", body = ProblemDetails),
|
||||||
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
(status = 409, description = "Desktop runtime is not ready", body = ProblemDetails),
|
||||||
(status = 503, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
(status = 502, description = "Desktop runtime health or input failed", body = ProblemDetails)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
async fn post_v1_desktop_keyboard_press(
|
async fn post_v1_desktop_keyboard_press(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue