Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions crates/tui/src/tools/subagent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1249,11 +1249,13 @@ impl SubAgentManager {
return false;
}
// Exclude persisted agents with no task_handle (they're not actually running)
let Some(handle) = agent.task_handle.as_ref() else {
if agent.task_handle.is_none() {
return false;
};
// Exclude agents whose task has finished (status will be updated to Completed shortly)
!handle.is_finished()
}
// Keep recently finished handles counted until the terminal
// status update has reconciled. Otherwise a fanout burst can
// refill the cap before the UI/state catches up (#2211).
true
Comment on lines +1252 to +1258
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check for agent.task_handle.is_none() followed by returning false and then returning true can be simplified to directly returning agent.task_handle.is_some(). This is more idiomatic and concise.

                // Keep recently finished handles counted until the terminal
                // status update has reconciled. Otherwise a fanout burst can
                // refill the cap before the UI/state catches up (#2211).
                agent.task_handle.is_some()

})
.count()
}
Expand Down
15 changes: 6 additions & 9 deletions crates/tui/src/tools/subagent/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,7 @@ fn test_running_count_ignores_running_status_without_task_handle() {
}

#[tokio::test]
async fn test_running_count_ignores_finished_task_handles() {
async fn test_running_count_counts_running_agents_until_status_reconciles() {
let mut manager = SubAgentManager::new(PathBuf::from("."), 1);
let (input_tx, _input_rx) = mpsc::unbounded_channel();
let mut agent = SubAgent::new(
Expand All @@ -953,17 +953,14 @@ async fn test_running_count_ignores_finished_task_handles() {
"boot_test".to_string(),
);
agent.status = SubAgentStatus::Running;
let handle = tokio::spawn(async {});
handle.await.expect("dummy task should finish immediately");
agent.task_handle = Some(tokio::spawn(async {}));
if let Some(handle) = agent.task_handle.as_ref() {
while !handle.is_finished() {
tokio::task::yield_now().await;
}
let finished_handle = tokio::spawn(async {});
while !finished_handle.is_finished() {
tokio::task::yield_now().await;
}
agent.task_handle = Some(finished_handle);
Comment on lines +956 to +960
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a busy-yield loop (while !finished_handle.is_finished() { tokio::task::yield_now().await; }) to wait for a spawned task to finish can be CPU-intensive and potentially flaky under heavy CI load. A more robust approach is to synchronize using a oneshot channel to signal completion before checking is_finished().

    let (tx, rx) = tokio::sync::oneshot::channel();
    let finished_handle = tokio::spawn(async move {
        let _ = tx.send(());
    });
    let _ = rx.await;
    while !finished_handle.is_finished() {
        tokio::task::yield_now().await;
    }
    agent.task_handle = Some(finished_handle);

manager.agents.insert(agent.id.clone(), agent);

assert_eq!(manager.running_count(), 0);
assert_eq!(manager.running_count(), 1);
}

#[test]
Expand Down
Loading