aboutsummaryrefslogtreecommitdiff
path: root/src/terminal
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-30 00:19:36 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-30 00:19:36 +0300
commit8b1d77acf6059e6688fb6a875c3176c95602b6c4 (patch)
treecb9bf5e138a4cfe098726cdaf3b06e0ef458288f /src/terminal
parent1db9695eecece3ddd2bcf43ac33c961daffd6004 (diff)
fix: keep powershell panes alive after agent ctrl-c
refs #860
Diffstat (limited to 'src/terminal')
-rw-r--r--src/terminal/state.rs49
1 files changed, 48 insertions, 1 deletions
diff --git a/src/terminal/state.rs b/src/terminal/state.rs
index e7923719..8fea0542 100644
--- a/src/terminal/state.rs
+++ b/src/terminal/state.rs
@@ -1,6 +1,6 @@
use std::collections::HashMap;
use std::path::PathBuf;
-#[cfg(test)]
+#[cfg(any(windows, test))]
use std::time::Duration;
use std::time::Instant;
@@ -91,6 +91,7 @@ pub struct TerminalState {
pub revision: u64,
pub launch_argv: Option<Vec<String>>,
pub respawn_shell_on_exit: bool,
+ recent_agent_process_exit_at: Option<Instant>,
pub pending_agent_resume_plan: Option<crate::agent_resume::AgentResumePlan>,
}
@@ -117,6 +118,7 @@ impl TerminalState {
revision: 0,
launch_argv: None,
respawn_shell_on_exit: false,
+ recent_agent_process_exit_at: None,
pending_agent_resume_plan: None,
}
}
@@ -131,6 +133,12 @@ impl TerminalState {
self
}
+ #[cfg(any(windows, test))]
+ pub(crate) fn agent_process_exited_within(&self, now: Instant, max_age: Duration) -> bool {
+ self.recent_agent_process_exit_at
+ .is_some_and(|exited_at| now.saturating_duration_since(exited_at) <= max_age)
+ }
+
pub fn with_pending_agent_resume_plan(
mut self,
plan: crate::agent_resume::AgentResumePlan,
@@ -246,6 +254,11 @@ impl TerminalState {
self.fallback_state = fallback_state;
self.fallback_visible_blocker = visible_blocker && fallback_state == AgentState::Blocked;
self.fallback_observed_at = Some(now);
+ if process_exited && agent.is_some() {
+ self.recent_agent_process_exit_at = Some(now);
+ } else if agent.is_some() {
+ self.recent_agent_process_exit_at = None;
+ }
if process_exited
&& self.hook_authority_not_newer_than(now)
&& self.hook_authority.as_ref().is_some_and(|authority| {
@@ -1118,6 +1131,7 @@ impl TerminalState {
self.last_agent_state_change_seq = None;
self.launch_argv = None;
self.respawn_shell_on_exit = false;
+ self.recent_agent_process_exit_at = None;
self.pending_agent_resume_plan = None;
self.clear_agent_name();
}
@@ -3657,6 +3671,39 @@ mod tests {
}
#[test]
+ fn agent_process_exit_tracks_recent_respawn_window() {
+ let mut terminal = test_terminal();
+ let now = std::time::Instant::now();
+
+ terminal.set_detected_state_with_screen_signals_at(
+ Some(Agent::OpenCode),
+ AgentState::Idle,
+ false,
+ false,
+ false,
+ true,
+ now,
+ );
+
+ assert!(terminal.agent_process_exited_within(now, Duration::from_secs(2)));
+ assert!(!terminal
+ .agent_process_exited_within(now + Duration::from_secs(3), Duration::from_secs(2)));
+
+ terminal.set_detected_state_with_screen_signals_at(
+ Some(Agent::OpenCode),
+ AgentState::Working,
+ false,
+ false,
+ true,
+ false,
+ now + Duration::from_secs(4),
+ );
+
+ assert!(!terminal
+ .agent_process_exited_within(now + Duration::from_secs(4), Duration::from_secs(2)));
+ }
+
+ #[test]
fn detected_conflict_clears_live_hook_but_preserves_session_ref() {
let mut terminal = test_terminal();
terminal.set_hook_authority_with_session_ref(