Merge branch 'code-branch-20251010-001947' into main

This commit is contained in:
James Peter
2025-10-10 14:57:18 +10:00
2 changed files with 218 additions and 17 deletions
+3 -1
View File
@@ -3778,8 +3778,9 @@ async fn exit_review_mode(
let developer_message = ResponseItem::Message {
id: None,
role: "user".to_string(),
content: vec![ContentItem::InputText { text: developer_text }],
content: vec![ContentItem::InputText { text: developer_text.clone() }],
};
let status = if review_output.is_some() {
"complete"
} else {
@@ -3789,6 +3790,7 @@ async fn exit_review_mode(
let mut metadata_payload = json!({
"type": "code_review_metadata",
"status": status,
"summary": developer_text,
});
if let Some(request) = active_request {
+215 -16
View File
@@ -2462,6 +2462,9 @@ impl ChatWidget<'_> {
return;
}
if role == "user" {
if text.starts_with("<user_action>") {
return;
}
if let Some(expected) = self.pending_dispatched_user_messages.front() {
if expected.trim() == text {
self.pending_dispatched_user_messages.pop_front();
@@ -8535,21 +8538,41 @@ impl ChatWidget<'_> {
fn finalize_sent_user_message(&mut self, message: UserMessage) {
let UserMessage {
display_text,
..
ordered_items,
} = message;
let combined_message_text = {
let mut buffer = String::new();
for item in &ordered_items {
if let InputItem::Text { text } = item {
if !buffer.is_empty() {
buffer.push('\n');
}
buffer.push_str(text);
}
}
let trimmed = buffer.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
};
if !display_text.is_empty() {
let key = self.next_req_key_prompt();
let state = history_cell::new_user_prompt(display_text.clone());
let _ = self.history_insert_plain_state_with_key(state, key, "prompt");
self.pending_user_prompts_for_next_turn =
self.pending_user_prompts_for_next_turn.saturating_add(1);
self.pending_dispatched_user_messages
.push_back(display_text.clone());
}
self.flush_pending_agent_notes();
if let Some(model_echo) = combined_message_text {
self.pending_dispatched_user_messages.push_back(model_echo);
}
if !display_text.is_empty() {
if let Err(e) = self
.code_op_tx
@@ -18666,6 +18689,32 @@ fi\n\
self.submit_user_message(msg);
}
fn submit_hidden_text_message_with_preface(&mut self, agent_text: String, preface: String) {
if agent_text.trim().is_empty() && preface.trim().is_empty() {
return;
}
use crate::chatwidget::message::UserMessage;
use code_core::protocol::InputItem;
let mut ordered = Vec::new();
if !preface.trim().is_empty() {
ordered.push(InputItem::Text { text: preface });
}
if !agent_text.trim().is_empty() {
ordered.push(InputItem::Text { text: agent_text });
}
if ordered.is_empty() {
return;
}
let msg = UserMessage {
display_text: String::new(),
ordered_items: ordered,
};
self.submit_user_message(msg);
}
/// Queue a note that will be delivered to the agent as a hidden system
/// message immediately before the next user input is sent. Notes are
/// drained in FIFO order so multiple updates retain their sequencing.
@@ -20936,6 +20985,67 @@ impl ChatWidget<'_> {
self.request_redraw();
}
fn auto_resolve_commit_sha(&self) -> Option<String> {
self.auto_resolve_state
.as_ref()
.and_then(|state| state.metadata.as_ref())
.and_then(|metadata| metadata.commit.clone())
}
fn auto_resolve_scope(&self) -> Option<String> {
self.auto_resolve_state
.as_ref()
.and_then(|state| state.metadata.as_ref())
.and_then(|metadata| metadata.scope.clone())
}
fn worktree_has_uncommitted_changes(&self) -> Option<bool> {
let output = Command::new("git")
.current_dir(&self.config.cwd)
.args(["status", "--short"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
Some(!stdout.trim().is_empty())
}
fn current_head_commit_sha(&self) -> Option<String> {
let output = Command::new("git")
.current_dir(&self.config.cwd)
.args(["rev-parse", "HEAD"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
if stdout.is_empty() {
None
} else {
Some(stdout)
}
}
fn commit_subject_for(&self, commit: &str) -> Option<String> {
let output = Command::new("git")
.current_dir(&self.config.cwd)
.args(["show", "-s", "--format=%s", commit])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
if stdout.is_empty() {
None
} else {
Some(stdout)
}
}
fn auto_resolve_should_block_auto_resume(&self) -> bool {
match self.auto_resolve_state.as_ref().map(|state| &state.phase) {
Some(AutoResolvePhase::PendingFix { .. })
@@ -21071,8 +21181,24 @@ impl ChatWidget<'_> {
preface.push_str("\n\nFindings:\n");
preface.push_str(&summary);
}
if self
.auto_resolve_scope()
.as_deref()
.is_some_and(|scope| scope.eq_ignore_ascii_case("commit"))
{
if let Some(commit) = self.auto_resolve_commit_sha() {
let short_sha: String = commit.chars().take(7).collect();
preface.push_str("\n\nCommit under review: ");
preface.push_str(&commit);
preface.push_str(" (short SHA ");
preface.push_str(&short_sha);
preface.push_str(
"). If you make changes to address these findings, amend this commit before responding so the review target reflects your fixes.",
);
}
}
self.auto_resolve_notice("Auto-resolve: asking the agent to verify and address the review findings.");
self.submit_text_message_with_preface(
self.submit_hidden_text_message_with_preface(
"Is this a real issue introduced by our changes? If so, please fix and resolve all similar issues.".to_string(),
preface,
);
@@ -21092,12 +21218,25 @@ impl ChatWidget<'_> {
preface.push_str(fix);
}
preface.push_str("\n\nReturn JSON: {\"status\": \"...\", \"rationale\": \"optional explanation\"}.");
if self
.auto_resolve_scope()
.as_deref()
.is_some_and(|scope| scope.eq_ignore_ascii_case("commit"))
{
if let Some(commit) = self.auto_resolve_commit_sha() {
let short_sha: String = commit.chars().take(7).collect();
preface.push_str("\n\nCommit under review: ");
preface.push_str(&commit);
preface.push_str(" (short SHA ");
preface.push_str(&short_sha);
preface.push_str(
"). Confirm that any fixes have been committed (amend the commit if necessary) before returning `no_issue`.",
);
}
}
self.auto_resolve_notice("Auto-resolve: requesting status JSON from the agent.");
self.submit_text_message_with_preface(
"Auto-resolve status check".to_string(),
preface,
);
self.submit_hidden_text_message_with_preface("Auto-resolve status check".to_string(), preface);
}
fn dispatch_auto_continue(&mut self, review: &ReviewOutputEvent) {
@@ -21110,7 +21249,7 @@ impl ChatWidget<'_> {
preface.push_str(&summary);
}
self.auto_resolve_notice("Auto-resolve: asking the agent to continue working on the findings.");
self.submit_text_message_with_preface("Please continue".to_string(), preface);
self.submit_hidden_text_message_with_preface("Please continue".to_string(), preface);
}
fn restart_auto_resolve_review(&mut self) {
@@ -21123,19 +21262,79 @@ impl ChatWidget<'_> {
let prep_label = format!(
"Preparing follow-up code review (attempt {bounded_attempt} of {max_attempts})"
);
let continued_prompt = format!(
"{}\n\n{}",
state_snapshot.prompt.trim_end(),
AUTO_RESOLVE_REVIEW_FOLLOWUP
);
let mut base_prompt = state_snapshot.prompt.trim_end().to_string();
if let Some(idx) = base_prompt.find(AUTO_RESOLVE_REVIEW_FOLLOWUP) {
base_prompt = base_prompt[..idx].trim_end().to_string();
}
let mut next_hint = state_snapshot.hint.clone();
let mut next_metadata = state_snapshot.metadata.clone();
if next_metadata
.as_ref()
.and_then(|meta| meta.scope.as_ref())
.is_some_and(|scope| scope.eq_ignore_ascii_case("commit"))
{
if let Some(new_commit) = self.current_head_commit_sha() {
let short_sha: String = new_commit.chars().take(7).collect();
let subject = self.commit_subject_for(&new_commit);
base_prompt = if let Some(subject) = subject {
format!(
"Review the code changes introduced by commit {new_commit} (\"{subject}\"). Provide prioritized, actionable findings."
)
} else {
format!(
"Review the code changes introduced by commit {new_commit}. Provide prioritized, actionable findings."
)
};
next_hint = format!("commit {short_sha}");
if let Some(meta) = next_metadata.as_mut() {
meta.commit = Some(new_commit.clone());
}
}
}
let mut continued_prompt = base_prompt.clone();
if let Some(last_review) = state_snapshot.last_review.as_ref() {
let recap = Self::auto_resolve_format_findings(last_review);
if !recap.is_empty() {
continued_prompt.push_str("\n\nPreviously reported findings to re-validate:\n");
continued_prompt.push_str(&recap);
}
}
if state_snapshot
.metadata
.as_ref()
.and_then(|meta| meta.scope.as_ref())
.is_some_and(|scope| scope.eq_ignore_ascii_case("commit"))
{
if let Some(commit) = state_snapshot
.metadata
.as_ref()
.and_then(|meta| meta.commit.clone())
{
if let Some(true) = self.worktree_has_uncommitted_changes() {
continued_prompt.push_str("\n\nNote: there are uncommitted changes in the working tree since commit ");
continued_prompt.push_str(&commit);
continued_prompt.push_str(
". Ensure the review covers the updated workspace rather than only the original commit snapshot.",
);
}
}
}
continued_prompt.push_str("\n\n");
continued_prompt.push_str(AUTO_RESOLVE_REVIEW_FOLLOWUP);
self.begin_review(
continued_prompt,
state_snapshot.hint.clone(),
next_hint.clone(),
Some(prep_label),
state_snapshot.metadata.clone(),
next_metadata.clone(),
);
if let Some(state) = self.auto_resolve_state.as_mut() {
state.phase = AutoResolvePhase::WaitingForReview;
state.prompt = base_prompt;
state.hint = next_hint;
state.metadata = next_metadata;
state.last_review = None;
state.last_fix_message = None;
}