mirror of
https://github.com/openai/codex.git
synced 2026-10-27 18:48:37 +00:00
Render watchdog countdown in subagent panel
This commit is contained in:
@@ -1121,6 +1121,9 @@ impl SubagentRegistry {
|
||||
name: info.name.clone(),
|
||||
status: info.status.clone(),
|
||||
is_watchdog: info.is_watchdog(),
|
||||
watchdog_countdown_started_at: info
|
||||
.is_watchdog()
|
||||
.then_some(info.running_started_at()),
|
||||
preview: running_preview(info),
|
||||
latest_update_at: info.latest_update_at,
|
||||
})
|
||||
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests.rs
|
||||
assertion_line: 2568
|
||||
expression: terminal.backend().vt100().screen().contents()
|
||||
---
|
||||
|
||||
╭───────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
@@ -10,7 +12,7 @@ expression: terminal.backend().vt100().screen().contents()
|
||||
╰───────────────────────────────────────╯
|
||||
|
||||
• Subagents (0s • no subagents running • esc to interrupt)
|
||||
• [#1] [watchdog] watchdog-agent idle — watchdog idle
|
||||
• [#1] [watchdog] watchdog-agent idle (59s) — watchdog idle
|
||||
|
||||
|
||||
› show current subagent state
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests.rs
|
||||
assertion_line: 464
|
||||
expression: rendered
|
||||
---
|
||||
• Agent message: Please review the latest diff from 019cbff7-558b-77d3-8653-8238ab5361ec
|
||||
@@ -2483,6 +2483,7 @@ async fn subagent_panel_is_not_flushed_into_transcript_history() {
|
||||
name: "user-request-derisk-implement".to_string(),
|
||||
status: AgentStatus::PendingInit,
|
||||
is_watchdog: true,
|
||||
watchdog_countdown_started_at: Some(Instant::now()),
|
||||
preview: "watchdog idle".to_string(),
|
||||
latest_update_at: Instant::now(),
|
||||
}],
|
||||
@@ -2532,6 +2533,7 @@ async fn subagent_panel_mounts_while_placeholder_active_cell_exists_snapshot() {
|
||||
name: "watchdog-agent".to_string(),
|
||||
status: AgentStatus::PendingInit,
|
||||
is_watchdog: true,
|
||||
watchdog_countdown_started_at: Some(Instant::now()),
|
||||
preview: "watchdog idle".to_string(),
|
||||
latest_update_at: Instant::now(),
|
||||
}],
|
||||
|
||||
@@ -507,6 +507,7 @@ pub(crate) struct SubagentPanelAgent {
|
||||
pub(crate) name: String,
|
||||
pub(crate) status: AgentStatus,
|
||||
pub(crate) is_watchdog: bool,
|
||||
pub(crate) watchdog_countdown_started_at: Option<Instant>,
|
||||
pub(crate) preview: String,
|
||||
pub(crate) latest_update_at: Instant,
|
||||
}
|
||||
@@ -530,7 +531,7 @@ impl SubagentPanelState {
|
||||
pub(crate) fn has_animating_agents(&self, now: Instant) -> bool {
|
||||
self.running_agents
|
||||
.iter()
|
||||
.any(|agent| should_shimmer(agent, now))
|
||||
.any(|agent| should_shimmer(agent, now) || has_watchdog_countdown(agent, now))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,7 +605,7 @@ impl HistoryCell for SubagentStatusCell {
|
||||
}
|
||||
spans.push(Span::from(agent.name.clone()));
|
||||
spans.push(" ".into());
|
||||
spans.push(status_span_for_panel(&agent));
|
||||
spans.push(status_span_for_panel(&agent, now));
|
||||
spans.push(" — ".dim());
|
||||
if self.animations_enabled && should_shimmer(&agent, now) {
|
||||
spans.extend(shimmer_spans(&preview));
|
||||
@@ -708,9 +709,15 @@ fn is_running_status(status: &AgentStatus) -> bool {
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
fn status_span_for_panel(agent: &SubagentPanelAgent) -> Span<'static> {
|
||||
fn status_span_for_panel(agent: &SubagentPanelAgent, now: Instant) -> Span<'static> {
|
||||
match &agent.status {
|
||||
AgentStatus::PendingInit if agent.is_watchdog => "idle".dim(),
|
||||
AgentStatus::PendingInit if agent.is_watchdog => {
|
||||
if let Some(countdown) = watchdog_countdown_remaining(agent, now) {
|
||||
format!("idle ({})", fmt_elapsed_compact(countdown.as_secs())).dim()
|
||||
} else {
|
||||
"idle".dim()
|
||||
}
|
||||
}
|
||||
AgentStatus::PendingInit | AgentStatus::Running => "running".cyan().bold(),
|
||||
AgentStatus::Interrupted => "interrupted".magenta(),
|
||||
AgentStatus::Completed(_) => "completed".green(),
|
||||
@@ -722,6 +729,7 @@ fn status_span_for_panel(agent: &SubagentPanelAgent) -> Span<'static> {
|
||||
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
const SUBAGENT_SHIMMER_WINDOW: Duration = Duration::from_secs(1);
|
||||
const WATCHDOG_COUNTDOWN: Duration = Duration::from_secs(60);
|
||||
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
fn should_shimmer(agent: &SubagentPanelAgent, now: Instant) -> bool {
|
||||
@@ -732,6 +740,26 @@ fn should_shimmer(agent: &SubagentPanelAgent, now: Instant) -> bool {
|
||||
&& now.saturating_duration_since(agent.latest_update_at) <= SUBAGENT_SHIMMER_WINDOW
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
fn has_watchdog_countdown(agent: &SubagentPanelAgent, now: Instant) -> bool {
|
||||
watchdog_countdown_remaining(agent, now).is_some_and(|remaining| remaining > Duration::ZERO)
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
fn watchdog_countdown_remaining(agent: &SubagentPanelAgent, now: Instant) -> Option<Duration> {
|
||||
if !agent.is_watchdog {
|
||||
return None;
|
||||
}
|
||||
if !matches!(agent.status, AgentStatus::PendingInit) {
|
||||
return None;
|
||||
}
|
||||
let Some(started_at) = agent.watchdog_countdown_started_at else {
|
||||
return None;
|
||||
};
|
||||
let elapsed = now.saturating_duration_since(started_at);
|
||||
Some(WATCHDOG_COUNTDOWN.saturating_sub(elapsed))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn status_label_span(status: &AgentStatus) -> Span<'static> {
|
||||
match status {
|
||||
@@ -3638,6 +3666,7 @@ mod tests {
|
||||
name: "watchdog-agent".to_string(),
|
||||
status: AgentStatus::PendingInit,
|
||||
is_watchdog: true,
|
||||
watchdog_countdown_started_at: Some(Instant::now()),
|
||||
preview: "monitor parent progress".to_string(),
|
||||
latest_update_at: Instant::now(),
|
||||
}],
|
||||
@@ -3647,10 +3676,11 @@ mod tests {
|
||||
|
||||
assert!(lines[0].contains("no subagents running"));
|
||||
assert!(lines[1].contains("[watchdog] watchdog-agent idle"));
|
||||
assert!(lines[1].contains("idle ("));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subagent_panel_animation_tick_ignores_idle_watchdogs() {
|
||||
fn subagent_panel_animation_tick_ticks_idle_watchdogs() {
|
||||
let state = Arc::new(Mutex::new(SubagentPanelState {
|
||||
started_at: Instant::now(),
|
||||
total_agents: 1,
|
||||
@@ -3660,6 +3690,32 @@ mod tests {
|
||||
name: "watchdog-agent".to_string(),
|
||||
status: AgentStatus::PendingInit,
|
||||
is_watchdog: true,
|
||||
watchdog_countdown_started_at: Some(Instant::now()),
|
||||
preview: "monitor parent progress".to_string(),
|
||||
latest_update_at: Instant::now(),
|
||||
}],
|
||||
}));
|
||||
let cell = SubagentStatusCell::new(state, /*animations_enabled*/ true);
|
||||
|
||||
assert!(cell.transcript_animation_tick().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subagent_panel_animation_tick_stops_after_countdown_expires() {
|
||||
let state = Arc::new(Mutex::new(SubagentPanelState {
|
||||
started_at: Instant::now(),
|
||||
total_agents: 1,
|
||||
running_count: 0,
|
||||
running_agents: vec![SubagentPanelAgent {
|
||||
ordinal: 1,
|
||||
name: "watchdog-agent".to_string(),
|
||||
status: AgentStatus::PendingInit,
|
||||
is_watchdog: true,
|
||||
watchdog_countdown_started_at: Some(
|
||||
Instant::now()
|
||||
.checked_sub(Duration::from_secs(61))
|
||||
.unwrap_or_else(Instant::now),
|
||||
),
|
||||
preview: "monitor parent progress".to_string(),
|
||||
latest_update_at: Instant::now(),
|
||||
}],
|
||||
@@ -3680,6 +3736,7 @@ mod tests {
|
||||
name: "worker-agent".to_string(),
|
||||
status: AgentStatus::Running,
|
||||
is_watchdog: false,
|
||||
watchdog_countdown_started_at: None,
|
||||
preview: "working".to_string(),
|
||||
latest_update_at: Instant::now(),
|
||||
}],
|
||||
@@ -3706,6 +3763,7 @@ mod tests {
|
||||
name: "worker-agent".to_string(),
|
||||
status: AgentStatus::Running,
|
||||
is_watchdog: false,
|
||||
watchdog_countdown_started_at: None,
|
||||
preview: "working".to_string(),
|
||||
latest_update_at: stale_update,
|
||||
}],
|
||||
|
||||
Reference in New Issue
Block a user