diff options
Diffstat (limited to 'src/pane.rs')
| -rw-r--r-- | src/pane.rs | 65 |
1 files changed, 65 insertions, 0 deletions
diff --git a/src/pane.rs b/src/pane.rs index 35534135..e5695675 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -363,6 +363,35 @@ async fn apply_agent_detection_publish_update( } const AGENT_MISS_CONFIRMATION_ATTEMPTS: u8 = 6; +const SELF_REPORTED_AGENT_SHELL_RECHECK: std::time::Duration = std::time::Duration::from_secs(1); + +/// Herdr cannot identify a self-reported agent's process, so the pane's shell +/// returning to an idle prompt is the only sign that the agent exited. +async fn report_self_reported_agent_shell_return( + active: &AtomicBool, + pid: u32, + now: std::time::Instant, + last_check: &mut Option<std::time::Instant>, + state_events: &mpsc::Sender<AppEvent>, + pane_id: PaneId, +) { + if pid == 0 || !active.load(Ordering::Acquire) { + *last_check = None; + return; + } + if last_check.is_some_and(|last| now.duration_since(last) < SELF_REPORTED_AGENT_SHELL_RECHECK) { + return; + } + *last_check = Some(now); + if crate::detect::pane_shell_is_idle(pid) { + let _ = state_events + .send(AppEvent::ReportedAgentShellReturned { + pane_id, + observed_at: now, + }) + .await; + } +} const PROCESS_RECHECK_IDENTIFIED: std::time::Duration = std::time::Duration::from_secs(5); const PROCESS_RECHECK_MISSING_FOREGROUND_GROUP: std::time::Duration = std::time::Duration::from_secs(30); @@ -766,6 +795,7 @@ fn spawn_basic_detection_task( terminal: Arc<PaneTerminal>, detection_content_seq: Arc<AtomicU64>, full_lifecycle_authority_active: Arc<AtomicBool>, + self_reported_agent_active: Arc<AtomicBool>, state_events: mpsc::Sender<AppEvent>, ) -> ( tokio::task::AbortHandle, @@ -797,6 +827,7 @@ fn spawn_basic_detection_task( let mut agent_startup_grace_until = None; let mut pending_idle = PendingIdleConfirmation::default(); let mut last_codex_prompt_ready = false; + let mut last_self_reported_shell_check = None; loop { let sleep_duration = if pending_idle.active() { @@ -845,6 +876,15 @@ fn spawn_basic_detection_task( let mut agent = agent_presence.current_agent(); let lifecycle_authority_active = full_lifecycle_authority_active.load(Ordering::Acquire); + report_self_reported_agent_shell_return( + &self_reported_agent_active, + pid, + now, + &mut last_self_reported_shell_check, + &state_events, + pane_id, + ) + .await; let foreground_pgid = (pid > 0) .then(|| crate::detect::foreground_process_group_id(pid)) .flatten(); @@ -1328,6 +1368,7 @@ pub struct PaneRuntime { content_write_lock: Arc<Mutex<()>>, detection_content_seq: Arc<AtomicU64>, full_lifecycle_authority_active: Arc<AtomicBool>, + self_reported_agent_active: Arc<AtomicBool>, detect_reset_notify: Arc<Notify>, pending_release: Arc<Mutex<Option<PendingAgentRelease>>>, preserve_processes_on_drop: bool, @@ -2461,12 +2502,14 @@ impl PaneRuntime { }; let full_lifecycle_authority_active = Arc::new(AtomicBool::new(false)); + let self_reported_agent_active = Arc::new(AtomicBool::new(false)); let (detect_handle, detect_reset_notify, pending_release) = spawn_basic_detection_task( pane_id, child_pid.clone(), terminal.clone(), detection_content_seq.clone(), full_lifecycle_authority_active.clone(), + self_reported_agent_active.clone(), events, ); @@ -2485,6 +2528,7 @@ impl PaneRuntime { content_write_lock, detection_content_seq, full_lifecycle_authority_active, + self_reported_agent_active, detect_reset_notify, pending_release, preserve_processes_on_drop: true, @@ -2544,6 +2588,7 @@ impl PaneRuntime { let content_seq = Arc::new(AtomicU64::new(0)); let detection_content_seq = Arc::new(AtomicU64::new(0)); let full_lifecycle_authority_active = Arc::new(AtomicBool::new(false)); + let self_reported_agent_active = Arc::new(AtomicBool::new(false)); { let child_pid = child_pid.clone(); let child_wait_completed = child_wait_completed.clone(); @@ -2667,6 +2712,7 @@ impl PaneRuntime { let state_events = events.clone(); let detection_content_seq = detection_content_seq.clone(); let full_lifecycle_authority_active_for_task = full_lifecycle_authority_active.clone(); + let self_reported_agent_active_for_task = self_reported_agent_active.clone(); let render_notify = render_notify.clone(); let render_dirty = render_dirty.clone(); let detect_reset_notify = Arc::new(Notify::new()); @@ -2698,6 +2744,7 @@ impl PaneRuntime { let mut agent_startup_grace_until = None; let mut pending_idle = PendingIdleConfirmation::default(); let mut last_codex_prompt_ready = false; + let mut last_self_reported_shell_check = None; tokio::time::sleep(Duration::from_millis(50)).await; @@ -2755,6 +2802,15 @@ impl PaneRuntime { let mut agent = agent_presence.current_agent(); let lifecycle_authority_active = full_lifecycle_authority_active_for_task.load(Ordering::Acquire); + report_self_reported_agent_shell_return( + &self_reported_agent_active_for_task, + pid, + now, + &mut last_self_reported_shell_check, + &state_events, + pane_id, + ) + .await; let process_probe_input = ProcessProbeInput { current_agent: agent, suppressed_agent, @@ -3075,6 +3131,7 @@ impl PaneRuntime { content_write_lock, detection_content_seq, full_lifecycle_authority_active, + self_reported_agent_active, detect_reset_notify, pending_release, preserve_processes_on_drop: false, @@ -3102,6 +3159,11 @@ impl PaneRuntime { self.detect_reset_notify.clone() } + pub fn set_self_reported_agent_active(&self, active: bool) { + self.self_reported_agent_active + .store(active, Ordering::Release); + } + pub fn set_full_lifecycle_authority_active(&self, active: bool) { let previous = self .full_lifecycle_authority_active @@ -3789,6 +3851,7 @@ impl PaneRuntime { content_write_lock: Arc::new(Mutex::new(())), detection_content_seq: Arc::new(AtomicU64::new(0)), full_lifecycle_authority_active: Arc::new(AtomicBool::new(false)), + self_reported_agent_active: Arc::new(AtomicBool::new(false)), detect_reset_notify: Arc::new(Notify::new()), pending_release: Arc::new(Mutex::new(None)), preserve_processes_on_drop: true, @@ -4940,6 +5003,7 @@ mod tests { )); let compression = TerminalCompressionTask::spawn(pane_id, terminal.clone()); let runtime = PaneRuntime { + self_reported_agent_active: Arc::new(AtomicBool::new(false)), cwd_process_exited: Arc::new(AtomicBool::new(false)), persistence_cwd: Mutex::new(None), pane_id, @@ -4979,6 +5043,7 @@ mod tests { )); let compression = TerminalCompressionTask::spawn(pane_id, terminal.clone()); let runtime = PaneRuntime { + self_reported_agent_active: Arc::new(AtomicBool::new(false)), cwd_process_exited: Arc::new(AtomicBool::new(false)), persistence_cwd: Mutex::new(None), pane_id, |
