use super::TuiContext;
use crate::sink::UiEvent;
use koda_core::child_agent::{AgentStatus, ChildTaskSnapshot};
use koda_core::engine::EngineEvent;
impl TuiContext {
pub(super) fn handle_idle_ui_event(&mut self, ui_event: UiEvent) {
match ui_event {
UiEvent::Engine(EngineEvent::ChildAgentActivity { task_id, kind, .. }) => {
tracing::debug!(
target: "koda_cli::diag::child_activity",
stage = "idle_handler",
task_id = task_id,
"ChildAgentActivity received in IDLE path -> record_activity"
);
self.child_activity.record_activity(task_id, &kind);
}
UiEvent::Engine(EngineEvent::ChildTaskUpdate { status, .. }) => {
let snapshot = self.session.bg_agents.snapshot();
if should_auto_resume(&status, &snapshot) {
self.auto_resume_pending = true;
tracing::debug!(
"auto_resume_pending=true (bg agent reached terminal status, no non-terminal entries remain)"
);
}
}
UiEvent::Engine(event) => {
self.renderer
.render_to_buffer(event, &mut self.scroll_buffer);
}
}
}
}
pub(crate) fn is_terminal(status: &AgentStatus) -> bool {
match status {
AgentStatus::Cancelled | AgentStatus::Completed { .. } | AgentStatus::Errored { .. } => {
true
}
AgentStatus::Pending | AgentStatus::Running { .. } => false,
_ => false,
}
}
pub(crate) fn should_auto_resume(
new_status: &AgentStatus,
registry_snapshot: &[ChildTaskSnapshot],
) -> bool {
is_terminal(new_status) && registry_snapshot.iter().all(|s| is_terminal(&s.status))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pending_and_running_are_not_terminal() {
assert!(!is_terminal(&AgentStatus::Pending));
assert!(!is_terminal(&AgentStatus::Running { iter: 0 }));
assert!(!is_terminal(&AgentStatus::Running { iter: 42 }));
}
#[test]
fn completed_errored_cancelled_are_terminal() {
assert!(is_terminal(&AgentStatus::Cancelled));
assert!(is_terminal(&AgentStatus::Completed {
summary: "ok".into()
}));
assert!(is_terminal(&AgentStatus::Errored {
error: "boom".into()
}));
}
fn snap(task_id: u32, status: AgentStatus) -> ChildTaskSnapshot {
ChildTaskSnapshot::for_testing(
task_id,
"t".into(),
"p".into(),
std::time::Duration::from_secs(1),
status,
None,
)
}
#[test]
fn no_resume_when_status_is_non_terminal() {
assert!(!should_auto_resume(&AgentStatus::Running { iter: 3 }, &[]));
assert!(!should_auto_resume(&AgentStatus::Pending, &[]));
}
#[test]
fn no_resume_when_other_bg_agents_still_running() {
let snap = vec![
snap(
1,
AgentStatus::Completed {
summary: "a".into(),
},
),
snap(2, AgentStatus::Running { iter: 5 }),
];
assert!(!should_auto_resume(
&AgentStatus::Completed {
summary: "a".into()
},
&snap
));
}
#[test]
fn resume_when_terminal_and_all_others_terminal() {
let snap = vec![
snap(
1,
AgentStatus::Completed {
summary: "a".into(),
},
),
snap(
2,
AgentStatus::Completed {
summary: "b".into(),
},
),
];
assert!(should_auto_resume(
&AgentStatus::Completed {
summary: "b".into()
},
&snap
));
}
#[test]
fn resume_when_terminal_and_snapshot_empty() {
assert!(should_auto_resume(
&AgentStatus::Errored {
error: "boom".into()
},
&[]
));
assert!(should_auto_resume(&AgentStatus::Cancelled, &[]));
}
}