diff --git a/core/agents/base.py b/core/agents/base.py index 242d25585..3a8dd05b1 100644 --- a/core/agents/base.py +++ b/core/agents/base.py @@ -61,6 +61,16 @@ async def send_message(self, message: str): """ await self.ui.send_message(message + "\n", source=self.ui_source) + async def send_modified_files(self, files: dict[str, str, str]): + """ + Send modified files to the user. + + Convenience method, uses `UIBase.send_modified_files()` to send the files, + setting the correct files. + :param files: Files to send. + """ + await self.ui.send_modified_files(files) + async def ask_question( self, question: str, diff --git a/core/agents/orchestrator.py b/core/agents/orchestrator.py index 2b6bdc439..457eb83e7 100644 --- a/core/agents/orchestrator.py +++ b/core/agents/orchestrator.py @@ -86,7 +86,7 @@ async def offline_changes_check(self): """ log.info("Checking for offline changes.") - modified_files = await self.state_manager.get_modified_files() + modified_files = await self.state_manager.get_modified_files_with_content() if self.state_manager.workspace_is_empty(): # NOTE: this will currently get triggered on a new project, but will do @@ -95,7 +95,7 @@ async def offline_changes_check(self): await self.state_manager.restore_files() elif modified_files: await self.send_message(f"We found {len(modified_files)} new and/or modified files.") - + await self.send_modified_files(modified_files) hint = "".join( [ "If you would like Pythagora to import those changes, click 'Yes'.\n", diff --git a/core/state/state_manager.py b/core/state/state_manager.py index a7eac3a98..8d15b4964 100644 --- a/core/state/state_manager.py +++ b/core/state/state_manager.py @@ -496,6 +496,49 @@ async def get_modified_files(self) -> list[str]: return modified_files + async def get_modified_files_with_content(self) -> list[dict]: + """ + Return a list of new or modified files from the file system, + including their paths, old content, and new content. + + :return: List of dictionaries containing paths, old content, + and new content for new or modified files. + """ + + modified_files = [] + files_in_workspace = self.file_system.list() + + for path in files_in_workspace: + content = self.file_system.read(path) + saved_file = self.current_state.get_file_by_path(path) + + # If there's a saved file, serialize its content; otherwise, set it to None + saved_file_content = saved_file.content.content if saved_file else None + if saved_file_content == content: + continue + + modified_files.append( + { + "path": path, + "file_old": saved_file_content, # Serialized content + "file_new": content, + } + ) + + # Handle files removed from disk + await self.current_state.awaitable_attrs.files + for db_file in self.current_state.files: + if db_file.path not in files_in_workspace: + modified_files.append( + { + "path": db_file.path, + "file_old": db_file.content.content, # Serialized content + "file_new": "", # Empty string as the file is removed + } + ) + + return modified_files + def workspace_is_empty(self) -> bool: """ Returns whether the workspace has any files in them or is empty. diff --git a/core/ui/base.py b/core/ui/base.py index 3f92c9546..39941b644 100644 --- a/core/ui/base.py +++ b/core/ui/base.py @@ -231,6 +231,17 @@ async def send_step_progress( """ raise NotImplementedError() + async def send_modified_files( + self, + modified_files: dict[str, str, str], + ): + """ + Send a list of modified files to the UI. + + :param modified_files: List of modified files. + """ + raise NotImplementedError() + async def send_run_command(self, run_command: str): """ Send a run command to the UI. diff --git a/core/ui/ipc_client.py b/core/ui/ipc_client.py index a3d09b9ac..07a6e7980 100644 --- a/core/ui/ipc_client.py +++ b/core/ui/ipc_client.py @@ -44,6 +44,7 @@ class MessageType(str, Enum): FEATURE_FINISHED = "featureFinished" GENERATE_DIFF = "generateDiff" CLOSE_DIFF = "closeDiff" + MODIFIED_FILES = "modifiedFiles" class Message(BaseModel): @@ -312,6 +313,15 @@ async def send_task_progress( }, ) + async def send_modified_files( + self, + modified_files: dict[str, str, str], + ): + await self._send( + MessageType.MODIFIED_FILES, + content={"files": modified_files}, + ) + async def send_step_progress( self, index: int, diff --git a/pythagora.db-journal b/pythagora.db-journal new file mode 100644 index 000000000..1dcb9f08b Binary files /dev/null and b/pythagora.db-journal differ