Skip to content
Open
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
21 changes: 20 additions & 1 deletion app/src/ai/blocklist/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,25 @@ impl BlocklistAIController {
&mut self,
ai_input: AIAgentInput,
ctx: &mut ModelContext<Self>,
) {
self.send_custom_ai_input_query_internal(ai_input, /*is_queued_prompt*/ false, ctx);
}

/// Sends a custom [`AIAgentInput`] query that was previously queued behind
/// an in-progress conversation.
pub fn send_queued_custom_ai_input_query(
&mut self,
ai_input: AIAgentInput,
ctx: &mut ModelContext<Self>,
) {
self.send_custom_ai_input_query_internal(ai_input, /*is_queued_prompt*/ true, ctx);
}

fn send_custom_ai_input_query_internal(
&mut self,
ai_input: AIAgentInput,
is_queued_prompt: bool,
ctx: &mut ModelContext<Self>,
) {
let participant_id = self.get_sharer_participant_id();
let which_task = match self.context_model.as_ref(ctx).selected_conversation_id(ctx) {
Expand All @@ -1230,7 +1249,7 @@ impl BlocklistAIController {
},
EntrypointType::UserInitiated,
participant_id,
/*is_queued_prompt*/ false,
is_queued_prompt,
ctx,
)
}
Expand Down
39 changes: 39 additions & 0 deletions app/src/terminal/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21169,6 +21169,16 @@ impl TerminalView {
review_comments,
};

if self.should_queue_inline_review(ctx) {
self.send_custom_ai_input_after_next_conversation_finished(
code_review_input,
/* show_close_button */ true,
/* show_send_now_button */ true,
ctx,
);
return Ok(());
}

if FeatureFlag::AgentView.is_enabled()
&& !self.agent_view_controller.as_ref(ctx).is_active()
{
Expand Down Expand Up @@ -21200,6 +21210,35 @@ impl TerminalView {
Ok(())
}

/// Returns `true` if a code review submission should be queued instead of sent immediately.
/// This occurs when queue mode is enabled and there's an in-progress or blocked conversation.
pub fn should_queue_inline_review(&self, ctx: &AppContext) -> bool {
if !FeatureFlag::QueueSlashCommand.is_enabled()
|| !self
.ai_context_model
.as_ref(ctx)
.is_queue_next_prompt_enabled()
{
return false;
}

let Some(conversation_id) = self
.ai_context_model
.as_ref(ctx)
.selected_conversation_id(ctx)
else {
return false;
};

BlocklistAIHistoryModel::as_ref(ctx)
.conversation(&conversation_id)
.is_some_and(|conversation| {
!conversation.is_empty()
&& (conversation.status().is_in_progress()
|| conversation.status().is_blocked())
})
}

/// Returns the CLI agent currently active in this terminal, if any.
pub fn active_cli_agent(&self, ctx: &AppContext) -> Option<super::CLIAgent> {
if !FeatureFlag::HoaCodeReview.is_enabled() {
Expand Down
101 changes: 97 additions & 4 deletions app/src/terminal/view/pending_user_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use warpui::{SingletonEntity, ViewContext};

use crate::{
ai::{
agent::{conversation::AIConversationId, CancellationReason},
agent::{conversation::AIConversationId, AIAgentInput, CancellationReason},
blocklist::block::{FinishReason, PendingUserQueryBlock, PendingUserQueryBlockEvent},
},
auth::AuthStateProvider,
Expand All @@ -26,12 +26,13 @@ impl TerminalView {
/// `show_close_button` controls the dismiss ("X") button; `show_send_now_button` controls
/// the "Send now" button that interrupts the active conversation and immediately submits
/// the queued prompt.
fn insert_pending_user_query_block(
fn insert_pending_user_query_block_with_send_now(
&mut self,
prompt: String,
show_close_button: bool,
show_send_now_button: bool,
ctx: &mut ViewContext<Self>,
on_send_now: impl Fn(&mut TerminalView, &mut ViewContext<TerminalView>) + 'static,
) {
self.remove_pending_user_query_block(ctx);
let auth_state = AuthStateProvider::as_ref(ctx).get().clone();
Expand All @@ -40,7 +41,6 @@ impl TerminalView {
.unwrap_or_else(|| "User".to_owned());
let profile_image_path = auth_state.user_photo_url();

let prompt_for_send_now = prompt.clone();
let handle = ctx.add_typed_action_view(|ctx| {
PendingUserQueryBlock::new(
prompt,
Expand All @@ -57,7 +57,7 @@ impl TerminalView {
me.remove_pending_user_query_block(ctx);
}
PendingUserQueryBlockEvent::SendNow => {
me.send_queued_prompt_now(prompt_for_send_now.clone(), ctx);
on_send_now(me, ctx);
}
});
}
Expand All @@ -73,6 +73,25 @@ impl TerminalView {
self.pending_user_query_view_id = Some(view_id);
}

fn insert_pending_user_query_block(
&mut self,
prompt: String,
show_close_button: bool,
show_send_now_button: bool,
ctx: &mut ViewContext<Self>,
) {
let prompt_for_send_now = prompt.clone();
self.insert_pending_user_query_block_with_send_now(
prompt,
show_close_button,
show_send_now_button,
ctx,
move |terminal_view, ctx| {
terminal_view.send_queued_prompt_now(prompt_for_send_now.clone(), ctx);
},
);
}

/// Inserts a pending user query block for a non-oz Cloud Mode run whose harness CLI
/// has not yet started.
/// The block shows the user's prompt with a "Queued" badge and no buttons: the
Expand Down Expand Up @@ -138,6 +157,80 @@ impl TerminalView {
});
}

fn send_queued_custom_ai_input_now(
&mut self,
ai_input: AIAgentInput,
ctx: &mut ViewContext<Self>,
) {
self.remove_pending_user_query_block(ctx);
if let Some(conversation_id) = self
.ai_context_model
.as_ref(ctx)
.selected_conversation_id(ctx)
{
self.ai_controller.update(ctx, |controller, ctx| {
controller.cancel_conversation_progress(
conversation_id,
CancellationReason::FollowUpSubmitted {
is_for_same_conversation: true,
},
ctx,
);
});
}

self.ai_controller.update(ctx, move |controller, ctx| {
controller.send_queued_custom_ai_input_query(ai_input, ctx);
});
}

/// Queues a structured AI input to be sent after the current conversation finishes.
/// This mirrors [`Self::send_user_query_after_next_conversation_finished`] but preserves
/// non-text inputs such as inline code review comment batches.
pub fn send_custom_ai_input_after_next_conversation_finished(
&mut self,
ai_input: AIAgentInput,
show_close_button: bool,
show_send_now_button: bool,
ctx: &mut ViewContext<Self>,
) {
let prompt = ai_input.user_query().unwrap_or_default();
if FeatureFlag::PendingUserQueryIndicator.is_enabled() {
let ai_input_for_send_now = ai_input.clone();
self.insert_pending_user_query_block_with_send_now(
prompt.clone(),
show_close_button,
show_send_now_button,
ctx,
move |terminal_view, ctx| {
terminal_view
.send_queued_custom_ai_input_now(ai_input_for_send_now.clone(), ctx);
},
);
}
self.queued_prompt_callback = Some(Box::new(move |terminal_view, reason, ctx| {
if FeatureFlag::PendingUserQueryIndicator.is_enabled() {
terminal_view.remove_pending_user_query_block(ctx);
}
match reason {
FinishReason::Complete => {
terminal_view.ai_controller.update(ctx, move |controller, ctx| {
controller.send_queued_custom_ai_input_query(ai_input, ctx);
});
}
FinishReason::Error
| FinishReason::Cancelled
| FinishReason::CancelledDuringRequestedCommandExecution => {
terminal_view.input.update(ctx, |input, ctx| {
if input.buffer_text(ctx).is_empty() {
input.replace_buffer_content(&prompt, ctx);
Comment thread
ssgamingop marked this conversation as resolved.
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.

⚠️ [IMPORTANT] This fallback restores only ai_input.user_query() (for code reviews, Address these comments) and drops the review batch/diff context when the active conversation is cancelled or errors, so queued review comments can be lost instead of recoverable. Preserve the structured input or keep the pending review available for resubmission.

}
});
}
}
}));
}

/// Shows a pending user query indicator and queues the query to be sent after
/// the current conversation finishes. If the conversation completes successfully,
/// the queued prompt is re-submitted through the normal input flow (so slash
Expand Down
14 changes: 12 additions & 2 deletions app/src/workspace/view/right_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,10 @@ impl RightPanelView {
/// Routes review comments to the best available terminal.
/// Tries the preferred terminal first, then falls back to other terminals
/// in the same repo working directory.
///
/// When queue mode is enabled with an in-progress conversation, code review comments
/// are routed through the inline review path to respect the queueing behavior,
/// regardless of whether a CLI agent is active.
fn route_review_comments(
&mut self,
code_review_view: &ViewHandle<CodeReviewView>,
Expand Down Expand Up @@ -1232,8 +1236,14 @@ impl RightPanelView {
.len();

let active_cli_agent = terminal_view.read(ctx, |t, ctx| t.active_cli_agent(ctx));

let (result, destination) = if active_cli_agent.is_some() {

// Check if code review should be queued instead of sent immediately.
// When queue mode is active with an in-progress conversation, we route through
// send_inline_review which respects the queuing mechanism, regardless of whether
// a CLI agent is active. This ensures queue mode is respected for code review.
let should_queue_review = terminal_view.read(ctx, |t, ctx| t.should_queue_inline_review(ctx));

let (result, destination) = if active_cli_agent.is_some() && !should_queue_review {
let r = terminal_view.update(ctx, |terminal, ctx| {
terminal.send_review_to_cli_agent_or_rich_input(&comments, ctx)
});
Expand Down