From 2552d101afe46b8622a7695f38f6cd1766dd1afc Mon Sep 17 00:00:00 2001 From: Can Celik Date: Sun, 27 Sep 2026 20:57:00 +0300 Subject: feat: let agents report their own resume command (#4687) * feat: let agents report their own resume command * fix: keep reported resume commands tied to accepted sessions and current owners --- src/detect/mod.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'src/detect/mod.rs') diff --git a/src/detect/mod.rs b/src/detect/mod.rs index 408ee669..7d7759d2 100644 --- a/src/detect/mod.rs +++ b/src/detect/mod.rs @@ -366,6 +366,11 @@ pub fn foreground_process_group_id(child_pid: u32) -> Option { crate::platform::foreground_process_group_id(child_pid) } +/// True when the pane's own shell is at its prompt with nothing running in it. +pub fn pane_shell_is_idle(child_pid: u32) -> bool { + crate::platform::available_pane_shell(child_pid).is_some() +} + fn normalized_process_name(process: &crate::platform::ForegroundProcess) -> String { let effective = process.argv0.as_deref().unwrap_or(&process.name); let lower_effective = effective.to_lowercase(); @@ -1926,6 +1931,49 @@ mod tests { ); } + #[cfg(target_os = "linux")] + #[test] + fn pane_shell_is_idle_tracks_foreground_command() { + use portable_pty::CommandBuilder; + use std::io::Write; + + fn wait_for(expected: bool, pid: u32) -> bool { + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + while std::time::Instant::now() < deadline { + if pane_shell_is_idle(pid) == expected { + return true; + } + std::thread::sleep(std::time::Duration::from_millis(20)); + } + false + } + + let pair = open_test_pty(); + let mut cmd = CommandBuilder::new("sh"); + cmd.arg("-i"); + cmd.env("ENV", "/dev/null"); + let mut child = pair.slave.spawn_command(cmd).expect("failed to spawn"); + let pid = child.process_id().expect("no pid"); + let mut reader = pair.master.try_clone_reader().expect("no reader"); + std::thread::spawn(move || { + let mut buf = [0u8; 1024]; + while matches!(std::io::Read::read(&mut reader, &mut buf), Ok(n) if n > 0) {} + }); + let mut writer = pair.master.take_writer().expect("no writer"); + + assert!(wait_for(true, pid), "interactive shell should start idle"); + writer.write_all(b"sleep 999\n").unwrap(); + assert!(wait_for(false, pid), "shell running a command is not idle"); + writer.write_all(&[3]).unwrap(); + assert!( + wait_for(true, pid), + "shell should be idle after the command ends" + ); + + child.kill().ok(); + child.wait().ok(); + } + #[cfg(target_os = "linux")] #[test] fn proc_stat_parsing_handles_spaces_in_comm() { -- cgit v1.2.3