feat: replace prompt with inform (fire-and-forget), logs default 20 lines

This commit is contained in:
Helios Agent 2026-03-06 03:13:42 +01:00
parent d2f77f8054
commit af0b6b5ddb
No known key found for this signature in database
GPG key ID: C8259547CD8309B5
5 changed files with 51 additions and 13 deletions

View file

@ -275,8 +275,8 @@ enum Commands {
device: String, device: String,
}, },
/// Show a MessageBox asking the user to do something /// Show a notification to the user (fire-and-forget, no response needed)
Prompt { Inform {
/// Device label /// Device label
device: String, device: String,
/// Message to display /// Message to display
@ -342,7 +342,7 @@ enum Commands {
/// Device label /// Device label
device: String, device: String,
/// Number of lines /// Number of lines
#[arg(long, default_value = "100")] #[arg(long, default_value = "20")]
lines: u32, lines: u32,
}, },
} }
@ -537,7 +537,7 @@ fn main() {
println!("All windows minimized on {}.", device); println!("All windows minimized on {}.", device);
} }
Commands::Prompt { Commands::Inform {
device, device,
message, message,
title, title,
@ -547,19 +547,14 @@ fn main() {
if let Some(t) = title { if let Some(t) = title {
body["title"] = json!(t); body["title"] = json!(t);
} }
let data = req( req(
&cfg, &cfg,
"POST", "POST",
&format!("/devices/{}/prompt", device), &format!("/devices/{}/inform", device),
Some(body), Some(body),
30, 10,
); );
let answer = data["answer"].as_str().unwrap_or(""); println!("User informed on {}.", device);
if !answer.is_empty() {
println!("{}", answer);
} else {
println!("Prompt confirmed.");
}
} }
Commands::Run { Commands::Run {

View file

@ -430,6 +430,26 @@ async fn handle_message(
} }
} }
ServerMessage::InformRequest { request_id, message, title } => {
let msg = message.clone();
let ttl = title.clone().unwrap_or_else(|| "Helios".to_string());
// Fire-and-forget: show MessageBox in background thread, don't block
std::thread::spawn(move || {
#[cfg(windows)]
unsafe {
use windows::core::PCWSTR;
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION, HWND_DESKTOP};
let msg_w: Vec<u16> = msg.encode_utf16().chain(std::iter::once(0)).collect();
let ttl_w: Vec<u16> = ttl.encode_utf16().chain(std::iter::once(0)).collect();
MessageBoxW(HWND_DESKTOP, PCWSTR(msg_w.as_ptr()), PCWSTR(ttl_w.as_ptr()), MB_OK | MB_ICONINFORMATION);
}
#[cfg(not(windows))]
let _ = (msg, ttl);
});
display::log_ok("inform", &message);
ClientMessage::Ack { request_id }
}
ServerMessage::PromptRequest { request_id, message, title: _ } => { ServerMessage::PromptRequest { request_id, message, title: _ } => {
display::prompt_waiting(&message); display::prompt_waiting(&message);
let answer = tokio::task::spawn_blocking(|| { let answer = tokio::task::spawn_blocking(|| {

View file

@ -58,6 +58,12 @@ pub enum ServerMessage {
message: String, message: String,
title: Option<String>, title: Option<String>,
}, },
/// Show a non-blocking notification to the user (fire-and-forget)
InformRequest {
request_id: Uuid,
message: String,
title: Option<String>,
},
/// Execute a shell command on the client /// Execute a shell command on the client
ExecRequest { ExecRequest {
request_id: Uuid, request_id: Uuid,

View file

@ -401,6 +401,22 @@ pub async fn clipboard_set(
} }
} }
/// POST /devices/:label/inform
pub async fn inform_user(
Path(label): Path<String>,
State(state): State<AppState>,
Json(body): Json<PromptBody>,
) -> impl IntoResponse {
match dispatch(&state, &label, "inform", |rid| ServerMessage::InformRequest {
request_id: rid,
message: body.message.clone(),
title: body.title.clone(),
}).await {
Ok(_) => (StatusCode::OK, Json(serde_json::json!({ "ok": true }))).into_response(),
Err(e) => e.into_response(),
}
}
/// POST /devices/:label/prompt /// POST /devices/:label/prompt
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct PromptBody { pub struct PromptBody {

View file

@ -49,6 +49,7 @@ async fn main() -> anyhow::Result<()> {
.route("/devices/:label/screenshot", post(api::request_screenshot)) .route("/devices/:label/screenshot", post(api::request_screenshot))
.route("/devices/:label/exec", post(api::request_exec)) .route("/devices/:label/exec", post(api::request_exec))
.route("/devices/:label/prompt", post(api::prompt_user)) .route("/devices/:label/prompt", post(api::prompt_user))
.route("/devices/:label/inform", post(api::inform_user))
.route("/devices/:label/windows", get(api::list_windows)) .route("/devices/:label/windows", get(api::list_windows))
.route("/devices/:label/windows/minimize-all", post(api::minimize_all)) .route("/devices/:label/windows/minimize-all", post(api::minimize_all))
.route("/devices/:label/logs", get(api::logs)) .route("/devices/:label/logs", get(api::logs))