aboutsummaryrefslogtreecommitdiff
path: root/src/pane.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/pane.rs')
-rw-r--r--src/pane.rs65
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,