Remove client-side plan approval interception

Let approval input flow through the LLM instead of being
short-circuited in the REPL. The LLM calls plan_approve
itself, which is cleaner (single input path) and more
flexible (no hardcoded misspelling list).
This commit is contained in:
Dhanji R. Prasanna
2026-02-06 20:16:11 +11:00
parent 7032e75fc6
commit 14112ff92e

View File

@@ -10,7 +10,6 @@ use tracing::{debug, error};
use g3_core::ui_writer::UiWriter;
use g3_core::Agent;
use g3_core::ToolCall;
use crate::commands::{handle_command, CommandResult};
use crate::display::{LoadedContent, print_loaded_status, print_workspace_path};
@@ -63,63 +62,6 @@ pub fn prepare_plan_mode_input(input: &str, is_first_plan_message: bool, in_plan
}
}
/// Check if the input is an approval command (for plan mode).
///
/// Recognizes: "a", "approve", "approved", and common misspellings.
pub fn is_approval_input(input: &str) -> bool {
let normalized = input.trim().to_lowercase();
// Strip trailing punctuation (!, ., ,)
let normalized = normalized.trim_end_matches(|c| c == '!' || c == '.' || c == ',');
// Exact matches
if matches!(normalized, "a" | "approve" | "approved" | "yes" | "y" | "ok") {
return true;
}
// Common misspellings of "approve" / "approved"
let misspellings = [
"approv", // missing 'e'
"aprove", // missing 'p'
"aproved", // missing 'p'
"aprrove", // transposed
"appprove", // extra 'p'
"apporve", // transposed
"approev", // transposed
"approvd", // missing 'e'
"approed", // missing 'v'
"approvee", // extra 'e'
"approveed", // extra 'e'
];
misspellings.iter().any(|&m| normalized == m)
}
/// Execute plan_approve tool directly without going through the LLM.
///
/// Returns (success, message) where success indicates if the plan was approved.
async fn execute_plan_approve_directly<W: UiWriter>(
agent: &mut Agent<W>,
output: &SimpleOutput,
) -> (bool, String) {
let tool_call = ToolCall {
tool: "plan_approve".to_string(),
args: serde_json::json!({}),
};
match agent.execute_tool_call(&tool_call).await {
Ok(result) => {
let success = result.contains("✅ Plan approved");
output.print(&result);
(success, result)
}
Err(e) => {
let msg = format!("❌ Failed to approve plan: {}", e);
output.print(&msg);
(false, msg)
}
}
}
/// Execute user input with template processing and auto-memory reminder.
///
/// This is the common path for both single-line and multiline input.
@@ -319,30 +261,6 @@ pub async fn run_interactive<W: UiWriter>(
// Single line input
let input = line.trim().to_string();
// In plan mode, check for approval input before anything else
if in_plan_mode && is_approval_input(&input) {
// Add to history
rl.add_history_entry(&input)?;
// Reprint input with formatting
reprint_formatted_input(&input, &prompt);
let (approved, result) = execute_plan_approve_directly(&mut agent, &output).await;
if approved {
// Exit plan mode on successful approval
in_plan_mode = false;
agent.set_plan_mode(false);
// Add synthetic assistant message so LLM knows plan was approved
use g3_providers::{Message, MessageRole};
let synthetic_msg = Message::new(MessageRole::Assistant, result);
agent.add_message_to_context(synthetic_msg);
}
// Stay in plan mode if approval failed
continue;
}
if input.is_empty() {
continue;
}
@@ -540,42 +458,6 @@ mod tests {
assert!(prompt.contains("awesome-app"));
}
#[test]
fn test_is_approval_input() {
// Exact matches
assert!(is_approval_input("a"));
assert!(is_approval_input("approve"));
assert!(is_approval_input("approved"));
assert!(is_approval_input("yes"));
assert!(is_approval_input("y"));
assert!(is_approval_input("ok"));
// Case insensitive
assert!(is_approval_input("APPROVE"));
assert!(is_approval_input("Approved"));
// Misspellings
assert!(is_approval_input("approv"));
assert!(is_approval_input("aprove"));
assert!(is_approval_input("appprove"));
// Non-approval inputs
assert!(!is_approval_input("no"));
assert!(!is_approval_input("reject"));
assert!(!is_approval_input("hello world"));
// Should NOT match partial words in longer text
assert!(!is_approval_input("I want to approve this"));
assert!(!is_approval_input("please approve the plan"));
assert!(!is_approval_input("a]ppro ve")); // gibberish with 'a' at start
// Should match with trailing punctuation
assert!(is_approval_input("approved!"));
assert!(is_approval_input("approve."));
assert!(is_approval_input("yes!"));
assert!(is_approval_input("ok,"));
}
// Tests for prepare_plan_mode_input
#[test]