diff --git a/src/drd/cli/commands.py b/src/drd/cli/commands.py index 610765b..bf72888 100644 --- a/src/drd/cli/commands.py +++ b/src/drd/cli/commands.py @@ -1,3 +1,4 @@ +import logging import click import sys import ast @@ -15,6 +16,10 @@ VERSION = "0.13.9" # Update this as you release new versions +logging.basicConfig(level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') + + def parse_multiline_input(input_string): try: # Use ast.literal_eval to safely evaluate the string as a Python literal diff --git a/src/drd/cli/monitor/error_resolver.py b/src/drd/cli/monitor/error_resolver.py index 5c821d4..a074ab3 100644 --- a/src/drd/cli/monitor/error_resolver.py +++ b/src/drd/cli/monitor/error_resolver.py @@ -7,10 +7,19 @@ from ..query.file_operations import get_files_to_modify from ...utils.file_utils import get_file_content from ...utils.input import confirm_with_user +import logging +import time def monitoring_handle_error_with_dravid(error, line, monitor): + time.sleep(2) + input = confirm_with_user("Allow Dravid to handle the current error?") + if not input: + return True + print_error(f"Error detected: {error}") + logger = logging.getLogger(__name__) + logger.info(f"Starting error handling for: {error}") error_message = str(error) error_type = type(error).__name__ @@ -72,18 +81,20 @@ def monitoring_handle_error_with_dravid(error, line, monitor): print_success("Fix applied.") - if requires_restart: - print_info("The applied fix requires a server restart.") - restart_input = confirm_with_user( - "Do you want to restart the server now? [y/N]: " - ) - if restart_input: - print_info("Requesting server restart...") - monitor.request_restart() - else: - print_info( - "Server restart postponed. You may need to restart manually if issues persist.") - else: - print_info("The applied fix does not require a server restart.") + logger.info(f"User response to restart: ") + # if requires_restart: + # print_info("The applied fix requires a server restart.") + # restart_input = confirm_with_user( + # "Do you want to restart the server now? [y/N]: " + # ) + # if restart_input: + print_info("Requesting server restart...") + monitor.request_restart() + # else: + # print_info( + # "Server restart postponed. You may need to restart manually if issues persist.") + # else: + # print_info("The applied fix does not require a server restart.") + logger.info("Error handling completed") return True diff --git a/src/drd/cli/monitor/input_handler.py b/src/drd/cli/monitor/input_handler.py index ef747b6..13e7fbd 100644 --- a/src/drd/cli/monitor/input_handler.py +++ b/src/drd/cli/monitor/input_handler.py @@ -5,16 +5,20 @@ from ...prompts.instructions import get_instruction_prompt from .input_parser import InputParser from ..query.main import execute_dravid_command +import logging class InputHandler: def __init__(self, monitor): self.monitor = monitor + self.logger = logging.getLogger(__name__) def handle_input(self): + self.logger.info("InputHandler triggered to handle input") print_info("\nNo more tasks to auto-process. What can I do next?") self._show_options() user_input = input("> ") + self.logger.info(f"Received user input: {user_input}") self._process_input(user_input) def _show_options(self): diff --git a/src/drd/cli/monitor/main.py b/src/drd/cli/monitor/main.py index 7766d30..701029b 100644 --- a/src/drd/cli/monitor/main.py +++ b/src/drd/cli/monitor/main.py @@ -26,19 +26,31 @@ def run_dev_server_with_monitoring(command: str): def handle_module_not_found(error_msg, monitor): - match = re.search( - r"(?:Cannot find module|Module not found|ImportError|No module named).*['\"](.*?)['\"]", error_msg, re.IGNORECASE) - if match: - module_name = match.group(1) - error = ImportError(f"Module '{module_name}' not found") - monitoring_handle_error_with_dravid(error, error_msg, monitor) + monitor.error_handling_in_progress.set() + try: + match = re.search( + r"(?:Cannot find module|Module not found|ImportError|No module named).*['\"](.*?)['\"]", error_msg, re.IGNORECASE) + if match: + module_name = match.group(1) + error = ImportError(f"Module '{module_name}' not found") + monitoring_handle_error_with_dravid(error, error_msg, monitor) + finally: + monitor.error_handling_in_progress.clear() def handle_syntax_error(error_msg, monitor): - error = SyntaxError(f"Syntax error detected: {error_msg}") - monitoring_handle_error_with_dravid(error, error_msg, monitor) + monitor.error_handling_in_progress.set() + try: + error = SyntaxError(f"Syntax error detected: {error_msg}") + monitoring_handle_error_with_dravid(error, error_msg, monitor) + finally: + monitor.error_handling_in_progress.clear() def handle_general_error(error_msg, monitor): - error = Exception(f"General error detected: {error_msg}") - monitoring_handle_error_with_dravid(error, error_msg, monitor) + monitor.error_handling_in_progress.set() + try: + error = Exception(f"General error detected: {error_msg}") + monitoring_handle_error_with_dravid(error, error_msg, monitor) + finally: + monitor.error_handling_in_progress.clear() diff --git a/src/drd/cli/monitor/output_monitor.py b/src/drd/cli/monitor/output_monitor.py index 047c777..882ba47 100644 --- a/src/drd/cli/monitor/output_monitor.py +++ b/src/drd/cli/monitor/output_monitor.py @@ -1,9 +1,10 @@ -import re -import time +import logging import select +import time import threading +import re -from ...utils import print_info, print_error +logger = logging.getLogger(__name__) class OutputMonitor: @@ -12,65 +13,68 @@ def __init__(self, monitor): self.thread = None self.last_output_time = None self.idle_detected = threading.Event() + self.stop_event = threading.Event() def start(self): + self.stop_event.clear() self.thread = threading.Thread( target=self._monitor_output, daemon=True) self.thread.start() + def stop(self): + self.stop_event.set() + if self.thread: + self.thread.join(timeout=5) + def _monitor_output(self): error_buffer = [] self.last_output_time = time.time() - while not self.monitor.should_stop.is_set(): - if self.monitor.process.poll() is not None and not self.monitor.processing_input.is_set(): - if not self.monitor.restart_requested.is_set(): - print_info("Server process ended unexpectedly.") - if self.monitor.retry_count < self.monitor.MAX_RETRIES: - self.monitor.retry_count += 1 - print_info( - f"Restarting... (Attempt {self.monitor.retry_count}/{self.monitor.MAX_RETRIES})") - self.monitor.perform_restart() - return # Exit the method after attempting restart - else: - print_error( - f"Server failed to start after {self.monitor.MAX_RETRIES} attempts. Exiting.") - self.monitor.stop() - break - else: - break # Exit the loop if restart is already requested - ready, _, _ = select.select( - [self.monitor.process.stdout], [], [], 0.1) - if self.monitor.process.stdout in ready: - line = self.monitor.process.stdout.readline() - if line: - print(line, end='', flush=True) - error_buffer.append(line) - if len(error_buffer) > 10: - error_buffer.pop(0) - self.last_output_time = time.time() - self.monitor.retry_count = 0 # Reset retry count on successful output - if not self.monitor.processing_input.is_set(): - self._check_for_errors(line, error_buffer) + while not self.stop_event.is_set(): + if self.monitor.process is None or self.monitor.process.poll() is not None: + logger.info( + "Server process ended or not started. Waiting for restart...") + time.sleep(1) + continue + + try: + ready, _, _ = select.select( + [self.monitor.process.stdout], [], [], 0.1) + if self.monitor.process.stdout in ready: + line = self.monitor.process.stdout.readline() + if line: + line = line.strip() + print(line, flush=True) + logger.debug(f"Server output: {line}") + error_buffer.append(line) + if len(error_buffer) > 10: + error_buffer.pop(0) + self.last_output_time = time.time() + self.monitor.retry_count = 0 + if not self.monitor.processing_input.is_set(): + self._check_for_errors(line, error_buffer) + else: + self._check_idle_state() else: self._check_idle_state() - else: - self._check_idle_state() + except Exception as e: + logger.error(f"Error in output monitoring: {str(e)}") + time.sleep(1) # Prevent rapid error logging - if self.monitor.restart_requested.is_set() and not self.monitor.processing_input.is_set(): - self.monitor.perform_restart() - return + logger.info("Output monitoring stopped.") def _check_for_errors(self, line, error_buffer): - for error_pattern, handler in self.monitor.error_handlers.items(): + for error_pattern in self.monitor.error_handlers.keys(): if re.search(error_pattern, line, re.IGNORECASE): - full_error = ''.join(error_buffer) - handler(full_error, self.monitor) + full_error = '\n'.join(error_buffer) + logger.error(f"Error detected: {full_error}") + self.monitor.handle_error(full_error) error_buffer.clear() break def _check_idle_state(self): current_time = time.time() if (current_time - self.last_output_time > 5 and + not self.monitor.error_handling_in_progress.is_set() and not self.monitor.processing_input.is_set()): self.idle_detected.set() diff --git a/src/drd/cli/monitor/server_monitor.py b/src/drd/cli/monitor/server_monitor.py index 7b5bc5c..ccb18d1 100644 --- a/src/drd/cli/monitor/server_monitor.py +++ b/src/drd/cli/monitor/server_monitor.py @@ -1,4 +1,4 @@ -import time +import re import threading import subprocess from queue import Queue @@ -7,13 +7,16 @@ from ...utils import print_info, print_success, print_error, print_header, print_prompt from ...metadata.project_metadata import ProjectMetadataManager +import logging -MAX_RETRIES = 3 + +logger = logging.getLogger(__name__) class DevServerMonitor: def __init__(self, project_dir: str, error_handlers: dict, command: str): self.project_dir = project_dir + self.MAX_RETRIES = 3 self.error_handlers = error_handlers self.command = command self.process = None @@ -26,77 +29,118 @@ def __init__(self, project_dir: str, error_handlers: dict, command: str): self.output_monitor = OutputMonitor(self) self.retry_count = 0 self.metadata_manager = ProjectMetadataManager(project_dir) + self.error_handling_in_progress = threading.Event() + self.error_handlers = { + str(pattern): handler for pattern, handler in error_handlers.items() + } def start(self): self.should_stop.clear() self.restart_requested.clear() - print_header( - f"Starting Dravid AI along with your process/server: {self.command}") + logger.info(f"Starting server with command: {self.command}") try: - self.process = start_process(self.command, self.project_dir) + self.process = self.start_process() self.output_monitor.start() self._main_loop() except Exception as e: - print_error(f"Failed to start server process: {str(e)}") - self.stop() - - def _main_loop(self): - try: - while not self.should_stop.is_set(): - if self.output_monitor.idle_detected.is_set(): - self.input_handler.handle_input() - self.output_monitor.idle_detected.clear() - time.sleep(0.1) # Small sleep to prevent busy-waiting - except KeyboardInterrupt: - print_info("Stopping server...") - finally: + logger.error(f"Failed to start server process: {str(e)}") self.stop() - def request_restart(self): - self.restart_requested.set() + def stop(self): + logger.info("Stopping server monitor...") + self.should_stop.set() + self.output_monitor.stop() + if self.process: + logger.info("Terminating server process...") + self.process.terminate() + try: + self.process.wait(timeout=10) + except subprocess.TimeoutExpired: + logger.warning("Process did not terminate, forcing...") + self.process.kill() + logger.info("Server monitor stopped.") def perform_restart(self): - print_info("Restarting server...") + logger.info("Restarting server...") if self.process: + logger.info("Terminating existing process...") self.process.terminate() - self.process.wait() + try: + self.process.wait(timeout=10) + except subprocess.TimeoutExpired: + logger.warning("Process did not terminate, forcing...") + self.process.kill() + + self.output_monitor.stop() try: - self.process = start_process(self.command, self.project_dir) + logger.info(f"Starting new process with command: {self.command}") + self.process = self.start_process() self.retry_count = 0 self.restart_requested.clear() - print_success("Server restarted successfully.") - print_info("Waiting for server output...") + self.recent_fixes.clear() + + self.output_monitor = OutputMonitor(self) + self.output_monitor.start() + + logger.info("Server restart completed.") + print("Server restarted successfully. Waiting for output...") except Exception as e: - print_error(f"Failed to restart server process: {str(e)}") + logger.error(f"Failed to restart server process: {str(e)}") + print(f"Failed to restart server process: {str(e)}") self.retry_count += 1 - if self.retry_count >= MAX_RETRIES: - print_error( - f"Server failed to start after {MAX_RETRIES} attempts. Exiting.") + if self.retry_count >= self.MAX_RETRIES: + logger.error( + f"Server failed to start after {self.MAX_RETRIES} attempts. Exiting.") + print( + f"Server failed to start after {self.MAX_RETRIES} attempts. Exiting.") self.stop() else: - print_info( - f"Retrying... (Attempt {self.retry_count + 1}/{MAX_RETRIES})") + logger.info( + f"Retrying... (Attempt {self.retry_count + 1}/{self.MAX_RETRIES})") + print( + f"Retrying... (Attempt {self.retry_count + 1}/{self.MAX_RETRIES})") self.request_restart() - def stop(self): - self.should_stop.set() - if self.process: - self.process.terminate() - self.process.wait() - if self.output_monitor.thread: - self.output_monitor.thread.join() - - -def start_process(command, cwd): - return subprocess.Popen( - command, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - stdin=subprocess.PIPE, - text=True, - bufsize=1, - universal_newlines=True, - shell=True, - cwd=cwd - ) + def start_process(self): + return subprocess.Popen( + self.command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + universal_newlines=True, + shell=True, + cwd=self.project_dir + ) + + def _main_loop(self): + try: + while not self.should_stop.is_set(): + if self.error_handling_in_progress.is_set(): + # Wait for error handling to complete + self.error_handling_in_progress.wait() + elif self.output_monitor.idle_detected.is_set(): + self.input_handler.handle_input() + self.output_monitor.idle_detected.clear() + else: + # Small sleep to prevent busy waiting + self.should_stop.wait(timeout=0.1) + except KeyboardInterrupt: + print_info("Stopping server...") + finally: + self.stop() + + def handle_error(self, error_msg): + self.error_handling_in_progress.set() + self.output_monitor.idle_detected.clear() + try: + for pattern, handler in self.error_handlers.items(): + if re.search(pattern, error_msg, re.IGNORECASE): + handler(error_msg, self) + break + finally: + self.error_handling_in_progress.clear() + + def request_restart(self): + self.restart_requested.set() diff --git a/src/drd/cli/query/file_operations.py b/src/drd/cli/query/file_operations.py index dae725a..563007e 100644 --- a/src/drd/cli/query/file_operations.py +++ b/src/drd/cli/query/file_operations.py @@ -10,7 +10,12 @@ def get_files_to_modify(query, project_context): file_query = get_files_to_modify_prompt(query, project_context) response = call_dravid_api_with_pagination( file_query, include_context=True) - return parse_file_list_response(response) + print(response, "response from files thing") + try: + return parse_file_list_response(response) + + finally: + return [] def find_file_with_dravid(filename, project_context, max_retries=2, current_retry=0): diff --git a/src/drd/cli/query/main.py b/src/drd/cli/query/main.py index 1d3dea0..2655273 100644 --- a/src/drd/cli/query/main.py +++ b/src/drd/cli/query/main.py @@ -10,6 +10,10 @@ def execute_dravid_command(query, image_path, debug, instruction_prompt, warn=None, reference_files=None): + query_len = len(query) + if query_len < 3: + print("query is", query_len) + return print_header("Starting Dravid AI ...") if warn: