mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
Merge branch 'code-branch-20251010-001947' into main
This commit is contained in:
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user