Skip to content
This repository was archived by the owner on Sep 23, 2025. It is now read-only.

Commit 2d5ced4

Browse files
committed
Restore structured UserFeedback handling in request-response pattern
**Corrected Flow:** 1. MCP tools call send_review_update() 2. Extension receives review data, shows to user 3. Extension responds with structured UserFeedback data 4. MCP tools format UserFeedback into clear LLM instructions **Changes:** - send_review_update() now returns UserFeedback instead of String - Restored format_user_feedback_message() method for LLM instructions - Both request_review and update_review process structured feedback - Extension expected to respond with UserFeedback JSON in response.data **Result:** Clean separation - IPC handles structured data, MCP tools format for LLM. Addresses #22 - implements structured UserFeedback in request-response cycle
1 parent ecc0436 commit 2d5ced4

File tree

3 files changed

+104
-27
lines changed

3 files changed

+104
-27
lines changed

server/src/ipc.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,19 @@ impl IPCCommunicator {
461461

462462
/// Send review update to VSCode extension and wait for user response
463463
/// This method blocks until the user provides feedback via the VSCode extension
464-
pub async fn send_review_update<T: serde::Serialize>(&self, review: &T) -> Result<String> {
464+
pub async fn send_review_update<T: serde::Serialize>(&self, review: &T) -> Result<crate::synthetic_pr::UserFeedback> {
465465
if self.test_mode {
466466
info!("Send review update called (test mode)");
467-
return Ok("User responded: This looks good to me!".to_string());
467+
return Ok(crate::synthetic_pr::UserFeedback {
468+
review_id: Some("test_review".to_string()),
469+
feedback_type: crate::synthetic_pr::FeedbackType::Comment,
470+
file_path: Some("test.rs".to_string()),
471+
line_number: Some(42),
472+
comment_text: Some("This looks good to me!".to_string()),
473+
completion_action: None,
474+
additional_notes: None,
475+
context_lines: None,
476+
});
468477
}
469478

470479
let shell_pid = {
@@ -485,15 +494,16 @@ impl IPCCommunicator {
485494
// Send message and wait for response
486495
let response = self.send_message_with_reply(message).await?;
487496

488-
// Extract user response text from the response
497+
// Parse UserFeedback from response data
489498
if let Some(data) = response.data {
490-
if let Some(user_text) = data.get("user_response").and_then(|v| v.as_str()) {
491-
Ok(user_text.to_string())
492-
} else {
493-
Ok("User completed the review.".to_string())
494-
}
499+
let user_feedback: crate::synthetic_pr::UserFeedback = serde_json::from_value(data)
500+
.map_err(IPCError::SerializationError)?;
501+
Ok(user_feedback)
495502
} else {
496-
Ok("User completed the review.".to_string())
503+
Err(IPCError::ConnectionFailed {
504+
path: "user_feedback".to_string(),
505+
source: std::io::Error::new(std::io::ErrorKind::InvalidData, "No user feedback data in response")
506+
})
497507
}
498508
}
499509

server/src/server.rs

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use tracing::info;
1717

1818
use crate::dialect::DialectInterpreter;
1919
use crate::ipc::IPCCommunicator;
20-
use crate::synthetic_pr::{RequestReviewParams, UpdateReviewParams};
20+
use crate::synthetic_pr::{RequestReviewParams, UpdateReviewParams, UserFeedback};
2121
use crate::types::{LogLevel, PresentReviewParams};
2222
use serde::{Deserialize, Serialize};
2323

@@ -84,6 +84,73 @@ impl DialecticServer {
8484
&self.ipc
8585
}
8686

87+
/// Format user feedback into clear instructions for the LLM
88+
fn format_user_feedback_message(&self, feedback: &UserFeedback) -> String {
89+
match feedback.feedback_type {
90+
crate::synthetic_pr::FeedbackType::Comment => {
91+
let file_path = feedback.file_path.as_deref().unwrap_or("unknown file");
92+
let line_number = feedback.line_number.unwrap_or(0);
93+
let comment_text = feedback.comment_text.as_deref().unwrap_or("(no comment)");
94+
95+
let context = if let Some(lines) = &feedback.context_lines {
96+
format!("\n\nCode context:\n```\n{}\n```", lines.join("\n"))
97+
} else {
98+
String::new()
99+
};
100+
101+
format!(
102+
"The user reviewed your code changes and left a comment on file `{}` at line {}:\n\n\
103+
User comment: '{}'{}\n\n\
104+
Please analyze the user's feedback and prepare a thoughtful response addressing their concern. \
105+
Do NOT modify any files on disk.\n\n\
106+
When ready, invoke the update_review tool with:\n\
107+
- review_id: '{}'\n\
108+
- action: AddComment\n\
109+
- comment: {{ response: 'Your response text here' }}\n\n\
110+
After responding, invoke update_review again with action: WaitForFeedback to continue the conversation.",
111+
file_path,
112+
line_number,
113+
comment_text,
114+
context,
115+
feedback.review_id.as_deref().unwrap_or("unknown")
116+
)
117+
}
118+
crate::synthetic_pr::FeedbackType::CompleteReview => {
119+
let action = feedback.completion_action.as_ref();
120+
let notes = feedback.additional_notes.as_deref().unwrap_or("");
121+
122+
let notes_section = if !notes.is_empty() {
123+
format!("\nAdditional notes: '{}'\n", notes)
124+
} else {
125+
String::new()
126+
};
127+
128+
match action {
129+
Some(crate::synthetic_pr::CompletionAction::RequestChanges) => format!(
130+
"User completed their review and selected: 'Request agent to make changes'{}\n\
131+
Based on the review discussion, please implement the requested changes. \
132+
You may now edit files as needed.\n\n\
133+
When finished, invoke: update_review(review_id: '{}', action: Approve)",
134+
notes_section,
135+
feedback.review_id.as_deref().unwrap_or("unknown")
136+
),
137+
Some(crate::synthetic_pr::CompletionAction::Checkpoint) => format!(
138+
"User completed their review and selected: 'Request agent to checkpoint this work'{}\n\
139+
Please commit the current changes and document the work completed.\n\n\
140+
When finished, invoke: update_review(review_id: '{}', action: Approve)",
141+
notes_section,
142+
feedback.review_id.as_deref().unwrap_or("unknown")
143+
),
144+
_ => format!(
145+
"User completed their review and selected: 'Return to agent without explicit request'{}\n\
146+
The review is complete. You may proceed as you see fit.",
147+
notes_section
148+
),
149+
}
150+
}
151+
}
152+
}
153+
87154
/// Ensure the message bus daemon is running for the given VSCode PID
88155
async fn ensure_daemon_running(vscode_pid: u32) -> Result<()> {
89156
crate::daemon::spawn_daemon_process(vscode_pid).await
@@ -359,7 +426,7 @@ impl DialecticServer {
359426
.await;
360427

361428
// Execute the synthetic PR creation
362-
let result = crate::synthetic_pr::request_review(params)
429+
let result = crate::synthetic_pr::harvest_review_data(params)
363430
.await
364431
.map_err(|e| {
365432
McpError::internal_error(
@@ -391,19 +458,17 @@ impl DialecticServer {
391458
.await;
392459

393460
// Send initial review to VSCode extension and wait for user response
394-
let user_response = self.ipc
395-
.send_review_update(&result)
396-
.await
397-
.map_err(|e| {
398-
McpError::internal_error(
399-
"Failed to send initial review",
400-
Some(serde_json::json!({
401-
"error": e.to_string()
402-
})),
403-
)
404-
})?;
461+
let user_feedback = self.ipc.send_review_update(&result).await.map_err(|e| {
462+
McpError::internal_error(
463+
"Failed to send initial review",
464+
Some(serde_json::json!({
465+
"error": e.to_string()
466+
})),
467+
)
468+
})?;
405469

406-
Ok(CallToolResult::success(vec![Content::text(user_response)]))
470+
let message = self.format_user_feedback_message(&user_feedback);
471+
Ok(CallToolResult::success(vec![Content::text(message)]))
407472
}
408473

409474
// ANCHOR: update_review_tool
@@ -440,7 +505,8 @@ impl DialecticServer {
440505
})?;
441506

442507
// 2. Send updated state to VSCode extension via IPC and wait for response
443-
let user_response = self.ipc
508+
let user_feedback = self
509+
.ipc
444510
.send_review_update(&updated_review)
445511
.await
446512
.map_err(|e| {
@@ -452,8 +518,9 @@ impl DialecticServer {
452518
)
453519
})?;
454520

455-
// 3. Return user's response to LLM
456-
Ok(CallToolResult::success(vec![Content::text(user_response)]))
521+
// 3. Return formatted user response to LLM
522+
let message = self.format_user_feedback_message(&user_feedback);
523+
Ok(CallToolResult::success(vec![Content::text(message)]))
457524
}
458525

459526
/// Get the status of the current synthetic pull request

server/src/synthetic_pr/mcp_tools.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ pub struct ReviewStatusResponse {
122122
/// # Returns
123123
/// * `Ok(ReviewResponse)` - Complete review data with files and comments
124124
/// * `Err(Box<dyn std::error::Error>)` - Git operation or file system error
125-
pub async fn request_review(
125+
pub async fn harvest_review_data(
126126
params: RequestReviewParams,
127127
) -> Result<ReviewData, Box<dyn std::error::Error>> {
128128
// Use provided repo path or default to current directory

0 commit comments

Comments
 (0)