mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
fix(tui/exec): clear lingering execs on final answer or fatal error
This commit is contained in:
@@ -5392,7 +5392,9 @@ impl ChatWidget<'_> {
|
||||
Some(HistoryDomainRecord::Plain(state)),
|
||||
);
|
||||
self.bottom_pane.set_task_running(false);
|
||||
self.exec.running_commands.clear();
|
||||
// Ensure any running exec/tool cells are finalized so spinners don't linger
|
||||
// after fatal errors.
|
||||
self.finalize_all_running_as_interrupted();
|
||||
self.stream.clear_all();
|
||||
self.stream_state.drop_streaming = false;
|
||||
self.agents_ready_to_start = false;
|
||||
@@ -11964,6 +11966,9 @@ impl ChatWidget<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow a fresh lingering-exec sweep even if the per-turn guard
|
||||
// was tripped before any commands started.
|
||||
self.cleared_lingering_execs_this_turn = false;
|
||||
self.ensure_lingering_execs_cleared();
|
||||
|
||||
self.stream_state.seq_answer_final = Some(event.event_seq);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use code_core::protocol::{
|
||||
AgentMessageEvent,
|
||||
Event,
|
||||
EventMsg,
|
||||
ErrorEvent,
|
||||
ExecCommandBeginEvent,
|
||||
ExecCommandEndEvent,
|
||||
TaskCompleteEvent,
|
||||
@@ -91,8 +93,6 @@ fn exec_cell_clears_after_patch_flow() {
|
||||
|
||||
#[test]
|
||||
fn exec_spinner_clears_after_final_answer() {
|
||||
use code_core::protocol::AgentMessageEvent;
|
||||
|
||||
let mut harness = ChatWidgetHarness::new();
|
||||
let mut seq = 0_u64;
|
||||
let call_id = "call_spinner".to_string();
|
||||
@@ -127,6 +127,54 @@ fn exec_spinner_clears_after_final_answer() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_cell_clears_after_task_started_final_answer_without_task_complete() {
|
||||
let mut harness = ChatWidgetHarness::new();
|
||||
let mut seq = 0_u64;
|
||||
let call_id = "call_final".to_string();
|
||||
let cwd = PathBuf::from("/tmp");
|
||||
|
||||
harness.handle_event(Event {
|
||||
id: "task-start".to_string(),
|
||||
event_seq: 0,
|
||||
msg: EventMsg::TaskStarted,
|
||||
order: Some(next_order_meta(1, &mut seq)),
|
||||
});
|
||||
|
||||
harness.handle_event(Event {
|
||||
id: "exec-begin".to_string(),
|
||||
event_seq: 1,
|
||||
msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: call_id.clone(),
|
||||
command: vec!["bash".into(), "-lc".into(), "echo pending".into()],
|
||||
cwd: cwd.clone(),
|
||||
parsed_cmd: Vec::new(),
|
||||
}),
|
||||
order: Some(next_order_meta(1, &mut seq)),
|
||||
});
|
||||
|
||||
harness.handle_event(Event {
|
||||
id: "answer-final".to_string(),
|
||||
event_seq: 2,
|
||||
msg: EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "done".into(),
|
||||
}),
|
||||
order: Some(next_order_meta(1, &mut seq)),
|
||||
});
|
||||
|
||||
let output = render_chat_widget_to_vt100(&mut harness, 80, 14);
|
||||
assert!(
|
||||
!output.contains("Running"),
|
||||
"exec should finalize after final answer even without TaskComplete:\n{}",
|
||||
output
|
||||
);
|
||||
assert!(
|
||||
output.contains("done"),
|
||||
"final answer should be visible in output:\n{}",
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_end_clears_cancelled_exec_spinner() {
|
||||
let mut harness = ChatWidgetHarness::new();
|
||||
@@ -332,3 +380,51 @@ fn exec_interrupts_flush_when_stream_idles() {
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn running_exec_is_finalized_when_error_event_arrives() {
|
||||
let mut harness = ChatWidgetHarness::new();
|
||||
let mut seq = 0_u64;
|
||||
let call_id = "call_error".to_string();
|
||||
let cwd = PathBuf::from("/tmp");
|
||||
|
||||
harness.handle_event(Event {
|
||||
id: "task-start".to_string(),
|
||||
event_seq: 0,
|
||||
msg: EventMsg::TaskStarted,
|
||||
order: Some(next_order_meta(1, &mut seq)),
|
||||
});
|
||||
|
||||
harness.handle_event(Event {
|
||||
id: "exec-begin-error".to_string(),
|
||||
event_seq: 1,
|
||||
msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: call_id.clone(),
|
||||
command: vec!["bash".into(), "-lc".into(), "pgrep something".into()],
|
||||
cwd: cwd.clone(),
|
||||
parsed_cmd: Vec::new(),
|
||||
}),
|
||||
order: Some(next_order_meta(1, &mut seq)),
|
||||
});
|
||||
|
||||
harness.handle_event(Event {
|
||||
id: "fatal-error".to_string(),
|
||||
event_seq: 2,
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message: "fatal: provider crashed".into(),
|
||||
}),
|
||||
order: None,
|
||||
});
|
||||
|
||||
let output = render_chat_widget_to_vt100(&mut harness, 80, 14);
|
||||
assert!(
|
||||
!output.contains("Running"),
|
||||
"exec cell should not linger after fatal error:\n{}",
|
||||
output
|
||||
);
|
||||
assert!(
|
||||
output.contains("fatal: provider crashed") || output.contains("Cancelled by user."),
|
||||
"error context should be visible after fatal error:\n{}",
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user