diff --git a/cli/src/flags.rs b/cli/src/flags.rs index 775b1c4..e21c54f 100644 --- a/cli/src/flags.rs +++ b/cli/src/flags.rs @@ -248,6 +248,7 @@ fn extract_config_path(args: &[String]) -> Option> { "--screenshot-format", "--idle-timeout", "--model", + "--humanize", ]; let mut i = 0; while i < args.len() { @@ -796,6 +797,21 @@ pub fn parse_flags(args: &[String]) -> Flags { i += 1; } } + "--humanize" => { + // Human-like input motion level (off|fast|human). Surface it as + // AGENT_BROWSER_HUMANIZE so the daemon — spawned as a child that + // inherits this process's env — picks it up and it overrides the + // adaptive detector. Applies when the session's daemon launches. + if let Some(s) = args.get(i + 1) { + match crate::native::humanize::HumanizeLevel::parse(s) { + Some(_) => std::env::set_var("AGENT_BROWSER_HUMANIZE", s), + None => eprintln!( + "warning: --humanize must be off|fast|human, got {s:?} (ignored)" + ), + } + i += 1; + } + } "--screenshot-dir" => { if let Some(s) = args.get(i + 1) { flags.screenshot_dir = Some(s.clone()); @@ -922,6 +938,7 @@ pub fn clean_args(args: &[String]) -> Vec { "--screenshot-format", "--idle-timeout", "--model", + "--humanize", ]; let mut i = 0; diff --git a/cli/src/native/interaction.rs b/cli/src/native/interaction.rs index cf93f82..ed701e9 100644 --- a/cli/src/native/interaction.rs +++ b/cli/src/native/interaction.rs @@ -350,9 +350,18 @@ pub async fn type_text_into_active_context( text: &str, delay_ms: Option, ) -> Result<(), String> { - let delay = delay_ms.unwrap_or(0); + // Per-character timing: an explicit `delay_ms` wins (caller asked for a + // fixed cadence); otherwise fall back to humanize — variable, human-like + // inter-keystroke gaps at Fast/Human, all-zero (instant) at Off. + let chars: Vec = text.chars().collect(); + let cadence: Vec = match delay_ms { + Some(d) => vec![std::time::Duration::from_millis(d); chars.len()], + None => { + humanize::keystroke_delays(chars.len(), humanize::active_level(), humanize::next_seed()) + } + }; - for ch in text.chars() { + for (i, ch) in chars.into_iter().enumerate() { if matches!(ch, '\n' | '\r' | '\t') { let (key, code, key_code) = char_to_key_info(ch); let text_str = key_text(&key); @@ -404,8 +413,9 @@ pub async fn type_text_into_active_context( .await?; } - if delay > 0 { - tokio::time::sleep(tokio::time::Duration::from_millis(delay)).await; + let gap = cadence[i]; + if !gap.is_zero() { + tokio::time::sleep(gap).await; } }