Improve CLI command inline pane and monitoring behavior
- Track user-initiated expansion state to avoid auto-collapse conflicts - Invert inline action visibility so terminal block hides while inline pane owns the expanded body - Add scrollable output rendering in requested command expanded view - Simplify CLI monitor nudge message and extract to reusable method - Show only latest user query and assistant text in monitor task transcript - Add handle_ctrl_c_for_conversation to status bar for child agent cancellation - Update rig_request tests for adjusted monitor nudge wording
This commit is contained in:
@@ -8730,6 +8730,47 @@ impl TerminalView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the conversation shown by AgentView when it has work that Ctrl+C should stop.
|
||||
/// The latest exchange can already be complete while a provider run, child agent, or command
|
||||
/// monitor is still alive, so looking only at the status bar's active exchange is insufficient.
|
||||
fn active_agent_conversation_to_cancel(
|
||||
&self,
|
||||
has_input_buffer: bool,
|
||||
ctx: &AppContext,
|
||||
) -> Option<AIConversationId> {
|
||||
if !FeatureFlag::AgentView.is_enabled()
|
||||
|| !self.agent_view_controller.as_ref(ctx).is_active()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let conversation_id = self
|
||||
.agent_view_controller
|
||||
.as_ref(ctx)
|
||||
.agent_view_state()
|
||||
.active_conversation_id()
|
||||
.or_else(|| {
|
||||
BlocklistAIHistoryModel::as_ref(ctx)
|
||||
.active_conversation(self.view_id)
|
||||
.map(|conversation| conversation.id())
|
||||
})?;
|
||||
|
||||
let conversation_has_progress = BlocklistAIHistoryModel::as_ref(ctx)
|
||||
.conversation(&conversation_id)
|
||||
.is_some_and(|conversation| {
|
||||
conversation.status().is_in_progress()
|
||||
&& (conversation.exchange_count() > 0 || has_input_buffer)
|
||||
});
|
||||
let command_is_monitored = {
|
||||
let model = self.model.lock();
|
||||
let active_block = model.block_list().active_block();
|
||||
active_block.is_active_and_long_running()
|
||||
&& active_block.ai_conversation_id() == Some(conversation_id)
|
||||
};
|
||||
|
||||
(conversation_has_progress || command_is_monitored).then_some(conversation_id)
|
||||
}
|
||||
|
||||
fn user_write_ctrl_c_to_pty(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
self.write_user_bytes_to_pty(vec![escape_sequences::C0::ETX], ctx);
|
||||
}
|
||||
@@ -8754,15 +8795,32 @@ impl TerminalView {
|
||||
|
||||
if FeatureFlag::AgentView.is_enabled() && self.agent_view_controller.as_ref(ctx).is_active()
|
||||
{
|
||||
if let Some(conversation_id) =
|
||||
self.active_agent_conversation_to_cancel(cleared_buffer_len > 0, ctx)
|
||||
{
|
||||
self.agent_view_controller.update(ctx, |controller, ctx| {
|
||||
controller.clear_pending_exit_confirmation(ctx);
|
||||
});
|
||||
// Cancel by conversation identity. This remains reliable when the latest exchange
|
||||
// has completed but a provider run, child agent, or command monitor is active.
|
||||
let command_is_for_conversation = {
|
||||
let model = self.model.lock();
|
||||
let active_block = model.block_list().active_block();
|
||||
active_block.is_active_and_long_running()
|
||||
&& active_block.ai_conversation_id() == Some(conversation_id)
|
||||
};
|
||||
if command_is_for_conversation {
|
||||
self.stop_local_agent_conversation(conversation_id, ctx);
|
||||
} else {
|
||||
self.cancel_active_conversation_via_status_bar(ctx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if cleared_buffer_len > 0 {
|
||||
self.agent_view_controller.update(ctx, |controller, ctx| {
|
||||
controller.clear_pending_exit_confirmation(ctx);
|
||||
});
|
||||
// Also cancel any in-progress conversation so that Ctrl+C while
|
||||
// composing a message (or after submitting when the buffer hasn't
|
||||
// cleared yet) properly stops the agent query and returns the
|
||||
// terminal to a ready state.
|
||||
self.cancel_active_conversation_via_status_bar(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9013,9 +9071,23 @@ impl TerminalView {
|
||||
});
|
||||
}
|
||||
|
||||
let conversation_id = self
|
||||
.agent_view_controller
|
||||
.as_ref(ctx)
|
||||
.agent_view_state()
|
||||
.active_conversation_id()
|
||||
.or_else(|| {
|
||||
BlocklistAIHistoryModel::as_ref(ctx)
|
||||
.active_conversation(self.view_id)
|
||||
.map(|conversation| conversation.id())
|
||||
});
|
||||
let status_bar = self.input.as_ref(ctx).agent_status_bar().clone();
|
||||
status_bar.update(ctx, |status_bar, ctx| {
|
||||
status_bar.handle_ctrl_c(ctx);
|
||||
if let Some(conversation_id) = conversation_id {
|
||||
status_bar.handle_ctrl_c_for_conversation(conversation_id, ctx);
|
||||
} else {
|
||||
status_bar.handle_ctrl_c(ctx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6024,6 +6024,55 @@ fn ctrl_c_buffer_clear_then_exit_requires_three_presses_in_agent_view() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ctrl_c_with_nonempty_agent_input_cancels_active_conversation() {
|
||||
App::test((), |mut app| async move {
|
||||
initialize_app_for_terminal_view(&mut app);
|
||||
FeatureFlag::AgentView.set_enabled(true);
|
||||
let terminal = add_window_with_terminal(&mut app, None);
|
||||
|
||||
let conversation_id = terminal.update(&mut app, |view, ctx| {
|
||||
let conversation_id =
|
||||
BlocklistAIHistoryModel::handle(ctx).update(ctx, |history, ctx| {
|
||||
history.start_new_conversation(view.view_id, false, false, false, ctx)
|
||||
});
|
||||
let stream_id = ResponseStreamId::new_for_test();
|
||||
BlocklistAIHistoryModel::handle(ctx).update(ctx, |history, ctx| {
|
||||
history
|
||||
.conversation_mut(&conversation_id)
|
||||
.expect("conversation should exist")
|
||||
.append_reassigned_exchange(
|
||||
&stream_id,
|
||||
exchange_with_inputs(vec![]),
|
||||
view.view_id,
|
||||
ctx,
|
||||
)
|
||||
.expect("exchange should append");
|
||||
});
|
||||
conversation_id
|
||||
});
|
||||
|
||||
terminal.update(&mut app, |view, ctx| {
|
||||
view.handle_input_event(
|
||||
&InputEvent::CtrlC {
|
||||
cleared_buffer_len: 4,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
|
||||
terminal.read(&app, |_, ctx| {
|
||||
assert_eq!(
|
||||
BlocklistAIHistoryModel::as_ref(ctx)
|
||||
.conversation(&conversation_id)
|
||||
.map(|conversation| conversation.status()),
|
||||
Some(&ConversationStatus::Cancelled),
|
||||
"Ctrl+C must cancel the agent instead of only clearing its draft"
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_action_ctrl_c_exit_agent_view_requires_confirmation() {
|
||||
App::test((), |mut app| async move {
|
||||
|
||||
Reference in New Issue
Block a user