aboutsummaryrefslogtreecommitdiff
path: root/src/pane.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-06 20:17:52 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-06 20:17:52 +0300
commit94b90043f98499a1ed3594dd9ea42b2aef62b93a (patch)
tree2f19157650b5010d91d815ee4afd4d54d2519fe2 /src/pane.rs
parentf7b0ee2f758440db3a3ff4cc727f879b420255d7 (diff)
fix: clear native agent detection after shell returns
fixes #66
Diffstat (limited to 'src/pane.rs')
-rw-r--r--src/pane.rs89
1 files changed, 87 insertions, 2 deletions
diff --git a/src/pane.rs b/src/pane.rs
index 51760ecf..71f35ca9 100644
--- a/src/pane.rs
+++ b/src/pane.rs
@@ -96,6 +96,14 @@ struct AgentDetectionPresence {
consecutive_misses: u8,
}
+fn should_clear_agent_for_foreground_shell(
+ previous_agent: Option<Agent>,
+ new_agent: Option<Agent>,
+ foreground_is_pane_shell: bool,
+) -> bool {
+ previous_agent.is_some() && new_agent.is_none() && foreground_is_pane_shell
+}
+
impl AgentDetectionPresence {
fn from_agent(current_agent: Option<Agent>) -> Self {
Self {
@@ -108,6 +116,16 @@ impl AgentDetectionPresence {
self.current_agent
}
+ fn clear_current_agent(&mut self) -> bool {
+ if self.current_agent.is_none() {
+ self.consecutive_misses = 0;
+ return false;
+ }
+ self.current_agent = None;
+ self.consecutive_misses = 0;
+ true
+ }
+
fn observe_process_probe(&mut self, identified_agent: Option<Agent>) -> bool {
match identified_agent {
Some(agent) => {
@@ -458,6 +476,8 @@ impl PaneRuntime {
let mut agent_presence = AgentDetectionPresence::from_agent(None);
let mut state = AgentState::Unknown;
let mut last_process_check = Instant::now();
+ let mut last_foreground_pgid = None;
+ let mut pending_foreground_shell_clear = false;
let mut last_claude_working_at = None;
tokio::time::sleep(Duration::from_millis(50)).await;
@@ -478,33 +498,50 @@ impl PaneRuntime {
_ = detect_reset.notified() => {
agent_presence = AgentDetectionPresence::from_agent(None);
state = AgentState::Unknown;
+ last_foreground_pgid = None;
+ pending_foreground_shell_clear = false;
last_claude_working_at = None;
}
}
let now = Instant::now();
let suppressed_agent = active_pending_release(&pending_release_for_task, now);
+ let pid = child_pid.load(Ordering::Acquire);
+ let foreground_pgid = (pid > 0 && agent_presence.current_agent().is_some())
+ .then(|| detect::foreground_process_group_id(pid))
+ .flatten();
+ let foreground_group_changed = foreground_pgid.is_some()
+ && last_foreground_pgid.is_some()
+ && foreground_pgid != last_foreground_pgid;
let should_check_process = suppressed_agent.is_some()
|| agent_presence.current_agent().is_none()
+ || foreground_group_changed
+ || pending_foreground_shell_clear
|| now.duration_since(last_process_check) >= PROCESS_RECHECK;
let mut agent_changed = false;
let mut agent = agent_presence.current_agent();
if should_check_process {
last_process_check = now;
- let pid = child_pid.load(Ordering::Acquire);
if pid > 0 {
let mut process_name = None;
let mut process_group_id = None;
+ let mut foreground_is_pane_shell = false;
let mut new_agent = None;
if let Some(job) = detect::foreground_job(pid) {
process_group_id = Some(job.process_group_id);
+ last_foreground_pgid = Some(job.process_group_id);
+ foreground_is_pane_shell =
+ job.processes.iter().any(|p| p.pid == pid);
let identified = detect::identify_agent_in_job(&job);
process_name = identified
.as_ref()
.map(|(_, process_name)| process_name.clone());
new_agent = identified.as_ref().map(|(agent, _)| *agent);
+ } else if foreground_pgid.is_some() {
+ process_group_id = foreground_pgid;
+ last_foreground_pgid = foreground_pgid;
}
if let Some(suppressed_agent) = suppressed_agent {
@@ -518,7 +555,28 @@ impl PaneRuntime {
}
let previous_agent = agent_presence.current_agent();
- if agent_presence.observe_process_probe(new_agent) {
+ let changed = if should_clear_agent_for_foreground_shell(
+ previous_agent,
+ new_agent,
+ foreground_is_pane_shell,
+ ) {
+ if state == AgentState::Idle {
+ pending_foreground_shell_clear = false;
+ agent_presence.clear_current_agent()
+ } else {
+ pending_foreground_shell_clear = true;
+ false
+ }
+ } else {
+ pending_foreground_shell_clear = false;
+ agent_presence.observe_process_probe(new_agent)
+ };
+ if new_agent.is_some() {
+ last_foreground_pgid = process_group_id;
+ } else if agent_presence.current_agent().is_none() {
+ last_foreground_pgid = None;
+ }
+ if changed {
agent = agent_presence.current_agent();
if let Some(process_name) = process_name {
info!(
@@ -995,6 +1053,33 @@ mod tests {
}
#[test]
+ fn foreground_shell_without_agent_is_immediate_clear_signal() {
+ assert!(should_clear_agent_for_foreground_shell(
+ Some(Agent::Claude),
+ None,
+ true
+ ));
+ }
+
+ #[test]
+ fn unknown_non_shell_foreground_job_is_not_immediate_clear_signal() {
+ assert!(!should_clear_agent_for_foreground_shell(
+ Some(Agent::Claude),
+ None,
+ false
+ ));
+ }
+
+ #[test]
+ fn foreground_agent_job_is_not_clear_signal() {
+ assert!(!should_clear_agent_for_foreground_shell(
+ Some(Agent::Claude),
+ Some(Agent::OpenCode),
+ true
+ ));
+ }
+
+ #[test]
fn transient_process_miss_keeps_current_agent_detected() {
let mut presence = AgentDetectionPresence::from_agent(Some(Agent::Pi));