aboutsummaryrefslogtreecommitdiff
path: root/src/terminal/state.rs
diff options
context:
space:
mode:
authorJJ Liebig <jonathan.liebig@gmail.com>2026-08-11 02:24:02 +0200
committerGitHub <noreply@github.com>2026-08-11 03:24:02 +0300
commit7ae4b056a0ca478e584fa282c45b528134cc80c9 (patch)
tree5eb8da51353327f2bb1b7ef35f76cafe9b471dee /src/terminal/state.rs
parent350f001320ec4a119f09c38157973126962a6121 (diff)
fix: wait for agent prompt readiness (#2537)
* fix: wait for agent prompt readiness refs #2410 * fix: return pane updates after tab bar command refs #2410 --------- Co-authored-by: Can Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'src/terminal/state.rs')
-rw-r--r--src/terminal/state.rs95
1 files changed, 82 insertions, 13 deletions
diff --git a/src/terminal/state.rs b/src/terminal/state.rs
index 56064a94..4b1fdaef 100644
--- a/src/terminal/state.rs
+++ b/src/terminal/state.rs
@@ -65,6 +65,7 @@ enum ManagedAgentPhase {
deadline: Instant,
observed_expected: bool,
},
+ Blocked,
Active,
}
@@ -144,6 +145,7 @@ pub struct TerminalState {
pub launch_argv: Option<Vec<String>>,
pub respawn_shell_on_exit: bool,
recent_agent_process_exit: Option<RecentAgentProcessExit>,
+ agent_process_acquisition_pending: bool,
pub pending_agent_resume_plan: Option<crate::agent_resume::AgentResumePlan>,
}
@@ -177,10 +179,43 @@ impl TerminalState {
launch_argv: None,
respawn_shell_on_exit: false,
recent_agent_process_exit: None,
+ agent_process_acquisition_pending: false,
pending_agent_resume_plan: None,
}
}
+ pub fn set_detected_agent_process_at(
+ &mut self,
+ agent: Agent,
+ now: Instant,
+ ) -> TerminalStateMutation {
+ let starts_acquisition = !self
+ .should_ignore_detected_state_under_full_lifecycle_hook(Some(agent), false)
+ && !self.detected_state_observed_before_release_suppression(Some(agent), now);
+ let mutation = self.set_detected_state_with_screen_signals_at(
+ Some(agent),
+ AgentState::Unknown,
+ false,
+ false,
+ false,
+ false,
+ now,
+ );
+ if starts_acquisition {
+ self.agent_process_acquisition_pending = true;
+ }
+ mutation
+ }
+
+ pub(crate) fn finish_agent_process_acquisition(&mut self) -> bool {
+ let reached_idle = self.agent_process_acquisition_pending && self.state == AgentState::Idle;
+ let suppress_completion = reached_idle && self.recent_agent_process_exit.is_none();
+ if reached_idle {
+ self.agent_process_acquisition_pending = false;
+ }
+ suppress_completion
+ }
+
pub(crate) fn terminal_title_stripped(&self) -> Option<String> {
self.terminal_title
.as_deref()
@@ -1877,8 +1912,12 @@ impl TerminalState {
}
pub fn managed_agent_launch_pending(&self) -> bool {
- self.managed_agent
- .is_some_and(|managed| matches!(managed.phase, ManagedAgentPhase::Pending { .. }))
+ self.managed_agent.is_some_and(|managed| {
+ matches!(
+ managed.phase,
+ ManagedAgentPhase::Pending { .. } | ManagedAgentPhase::Blocked
+ )
+ })
}
pub fn managed_agent_interactive_ready(&self) -> bool {
@@ -1911,7 +1950,7 @@ impl TerminalState {
ManagedAgentPhase::Pending {
observed_expected, ..
} => observed_expected || known_agent == Some(managed.kind),
- ManagedAgentPhase::Active => false,
+ ManagedAgentPhase::Blocked | ManagedAgentPhase::Active => false,
};
let clear = process_exited
|| known_agent.is_some_and(|agent| agent != managed.kind)
@@ -1922,20 +1961,35 @@ impl TerminalState {
self.clear_agent_name();
return true;
}
+ if managed.phase == ManagedAgentPhase::Blocked {
+ if known_agent == Some(managed.kind) && self.state == AgentState::Idle {
+ self.managed_agent = Some(ManagedAgent {
+ kind: managed.kind,
+ phase: ManagedAgentPhase::Active,
+ });
+ return true;
+ }
+ return false;
+ }
if let ManagedAgentPhase::Pending {
ready_after,
deadline,
observed_expected: previous_observed_expected,
} = managed.phase
{
+ if known_agent == Some(managed.kind) && self.state == AgentState::Blocked {
+ self.managed_agent = Some(ManagedAgent {
+ kind: managed.kind,
+ phase: ManagedAgentPhase::Blocked,
+ });
+ return true;
+ }
if now >= deadline {
self.clear_agent_name();
return true;
}
if ready_after.is_none_or(|ready_after| now >= ready_after) {
- if known_agent == Some(managed.kind)
- && matches!(self.state, AgentState::Idle | AgentState::Blocked)
- {
+ if known_agent == Some(managed.kind) && self.state == AgentState::Idle {
self.managed_agent = Some(ManagedAgent {
kind: managed.kind,
phase: ManagedAgentPhase::Active,
@@ -2003,6 +2057,7 @@ impl TerminalState {
self.launch_argv = None;
self.respawn_shell_on_exit = false;
self.recent_agent_process_exit = None;
+ self.agent_process_acquisition_pending = false;
self.pending_agent_resume_plan = None;
self.clear_agent_name();
}
@@ -2143,7 +2198,7 @@ mod tests {
}
#[test]
- fn managed_agent_activates_only_after_matching_settled_detection() {
+ fn managed_agent_readiness_tracks_detection_state() {
let mut terminal = test_terminal();
let now = Instant::now();
terminal.begin_managed_agent(
@@ -2153,23 +2208,35 @@ mod tests {
Duration::from_millis(100),
Duration::from_secs(1),
);
- terminal.set_detected_state(Some(Agent::Pi), AgentState::Idle);
+ terminal.set_detected_state(Some(Agent::Pi), AgentState::Unknown);
assert!(terminal.managed_agent_launch_pending());
assert!(!terminal.managed_agent_interactive_ready());
assert!(terminal.reconcile_managed_agent_at(now + Duration::from_millis(100), false));
- assert!(!terminal.managed_agent_launch_pending());
- assert!(terminal.managed_agent_interactive_ready());
- assert_eq!(terminal.agent_name.as_deref(), Some("reviewer"));
+ assert!(terminal.managed_agent_launch_pending());
terminal.set_detected_state(Some(Agent::Pi), AgentState::Working);
+ assert!(!terminal.reconcile_managed_agent_at(now + Duration::from_millis(101), false));
+
+ terminal.set_detected_state(Some(Agent::Pi), AgentState::Blocked);
+ assert!(terminal.reconcile_managed_agent_at(now + Duration::from_millis(102), false));
+ assert!(terminal.managed_agent_launch_pending());
+ assert!(!terminal.managed_agent_interactive_ready());
+ assert_eq!(terminal.next_managed_agent_deadline(), None);
+ assert_eq!(terminal.agent_name.as_deref(), Some("reviewer"));
+ assert!(!terminal.reconcile_managed_agent_at(now + Duration::from_secs(2), false));
+ assert_eq!(terminal.agent_name.as_deref(), Some("reviewer"));
+
+ terminal.set_detected_state(Some(Agent::Pi), AgentState::Idle);
+ assert!(terminal.reconcile_managed_agent_at(now + Duration::from_secs(2), false));
+ assert!(!terminal.managed_agent_launch_pending());
assert!(terminal.managed_agent_interactive_ready());
terminal.set_detected_state(None, AgentState::Unknown);
assert!(terminal.managed_agent_interactive_ready());
- assert!(!terminal.reconcile_managed_agent_at(now + Duration::from_millis(101), false));
+ assert!(!terminal.reconcile_managed_agent_at(now + Duration::from_secs(2), false));
assert_eq!(terminal.agent_name.as_deref(), Some("reviewer"));
- assert!(terminal.reconcile_managed_agent_at(now + Duration::from_millis(102), true));
+ assert!(terminal.reconcile_managed_agent_at(now + Duration::from_secs(2), true));
assert_eq!(terminal.agent_name, None);
}
@@ -5529,6 +5596,7 @@ mod tests {
session_ref: crate::agent_resume::AgentSessionRef::id("codex-session").unwrap(),
});
terminal.set_detected_state(Some(Agent::Codex), AgentState::Idle);
+ terminal.set_detected_agent_process_at(Agent::Codex, Instant::now());
terminal.clear_agent_runtime_identity_after_respawn();
@@ -5537,6 +5605,7 @@ mod tests {
assert!(terminal.agent_name.is_none());
assert!(terminal.persisted_agent_session.is_none());
assert!(!terminal.respawn_shell_on_exit);
+ assert!(!terminal.finish_agent_process_acquisition());
}
#[test]