diff --git a/core/agents/code_monkey.py b/core/agents/code_monkey.py index 247befa6..46ee2f6f 100644 --- a/core/agents/code_monkey.py +++ b/core/agents/code_monkey.py @@ -72,12 +72,13 @@ class CodeMonkey(BaseAgent): attempt = 1 feedback = None + iterations = self.current_state.iterations llm = self.get_llm() convo = self._get_task_convo().template( "implement_changes", file_name=file_name, file_content=file_content, - instructions=task["instructions"], + instructions=iterations[-1]["description"] if iterations else task["instructions"], ) if feedback: convo.assistant(f"```\n{self.prev_response.data['new_content']}\n```\n").template( diff --git a/core/agents/developer.py b/core/agents/developer.py index c2b1e91c..75960f19 100644 --- a/core/agents/developer.py +++ b/core/agents/developer.py @@ -67,20 +67,6 @@ class Developer(BaseAgent): if self.prev_response and self.prev_response.type == ResponseType.TASK_REVIEW_FEEDBACK: return await self.breakdown_current_iteration(self.prev_response.data["feedback"]) - # If any of the files are missing metadata/descriptions, those need to be filled-in - missing_descriptions = [file.path for file in self.current_state.files if not file.meta.get("description")] - if missing_descriptions: - log.debug(f"Some files are missing descriptions: {', '.join(missing_descriptions)}, reqesting analysis") - return AgentResponse.describe_files(self) - - log.debug( - f"Current state files: {len(self.current_state.files)}, relevant {self.current_state.relevant_files or []}" - ) - # Check which files are relevant to the current task - if self.current_state.files and self.current_state.relevant_files is None: - await self.get_relevant_files() - return AgentResponse.done(self) - if not self.current_state.unfinished_tasks: log.warning("No unfinished tasks found, nothing to do (why am I called? is this a bug?)") return AgentResponse.done(self) @@ -133,6 +119,8 @@ class Developer(BaseAgent): self.current_state.current_task["description"], source, "in-progress", + self.current_state.get_source_index(source), + self.current_state.tasks, ) llm = self.get_llm() # FIXME: In case of iteration, parse_task depends on the context (files, tasks, etc) set there. @@ -172,11 +160,18 @@ class Developer(BaseAgent): self.current_state.current_task["description"], source, "in-progress", + self.current_state.get_source_index(source), + self.current_state.tasks, ) log.debug(f"Breaking down the current task: {task['description']}") await self.send_message("Thinking about how to implement this task ...") + log.debug(f"Current state files: {len(self.current_state.files)}, relevant {self.current_state.relevant_files}") + # Check which files are relevant to the current task + if self.current_state.files and self.current_state.relevant_files is None: + await self.get_relevant_files() + current_task_index = self.current_state.tasks.index(task) llm = self.get_llm() @@ -236,6 +231,7 @@ class Developer(BaseAgent): "id": uuid4().hex, "completed": False, "source": source, + "iteration_index": len(self.current_state.iterations), **step.model_dump(), } for step in response.steps @@ -248,6 +244,7 @@ class Developer(BaseAgent): "completed": False, "type": "review_task", "source": source, + "iteration_index": len(self.current_state.iterations), }, ] log.debug(f"Next steps: {self.next_state.unfinished_steps}") diff --git a/core/agents/orchestrator.py b/core/agents/orchestrator.py index b27f4858..9bf4d561 100644 --- a/core/agents/orchestrator.py +++ b/core/agents/orchestrator.py @@ -150,7 +150,15 @@ class Orchestrator(BaseAgent): # If there are any new or modified files changed outside Pythagora, # this is a good time to add them to the project. If any of them have # INPUT_REQUIRED, we'll first ask the user to provide the required input. - return await self.import_files() + import_files_response = await self.import_files() + + # If any of the files are missing metadata/descriptions, those need to be filled-in + missing_descriptions = [file.path for file in self.current_state.files if not file.meta.get("description")] + if missing_descriptions: + log.debug(f"Some files are missing descriptions: {', '.join(missing_descriptions)}, requesting analysis") + return AgentResponse.describe_files(self) + + return import_files_response def create_agent(self, prev_response: Optional[AgentResponse]) -> BaseAgent: state = self.current_state @@ -291,7 +299,7 @@ class Orchestrator(BaseAgent): async def update_stats(self): if self.current_state.steps and self.current_state.current_step: source = self.current_state.current_step.get("source") - source_steps = [s for s in self.current_state.steps if s.get("source") == source] + source_steps = self.current_state.get_last_iteration_steps() await self.ui.send_step_progress( source_steps.index(self.current_state.current_step) + 1, len(source_steps), diff --git a/core/agents/task_completer.py b/core/agents/task_completer.py index e782dcee..20ec3bf9 100644 --- a/core/agents/task_completer.py +++ b/core/agents/task_completer.py @@ -14,12 +14,16 @@ class TaskCompleter(BaseAgent): self.next_state.action = f"Task #{current_task_index1} complete" self.next_state.complete_task() await self.state_manager.log_task_completed() + tasks = self.current_state.tasks + source = self.current_state.current_epic.get("source", "app") await self.ui.send_task_progress( - self.current_state.tasks.index(self.current_state.current_task) + 1, - len(self.current_state.tasks), + tasks.index(self.current_state.current_task) + 1, + len(tasks), self.current_state.current_task["description"], - self.current_state.current_epic.get("source", "app"), + source, "done", + self.current_state.get_source_index(source), + tasks, ) return AgentResponse.done(self) diff --git a/core/agents/troubleshooter.py b/core/agents/troubleshooter.py index 8744f05c..5b3b5de7 100644 --- a/core/agents/troubleshooter.py +++ b/core/agents/troubleshooter.py @@ -46,7 +46,7 @@ class Troubleshooter(IterationPromptMixin, BaseAgent): # Developer sets iteration as "completed" when it generates the step breakdown, so we can't # use "current_iteration" here - last_iteration = self.current_state.iterations[-1] if self.current_state.iterations else None + last_iteration = self.current_state.iterations[-1] if len(self.current_state.iterations) >= 3 else None should_iterate, is_loop, user_feedback = await self.get_user_feedback( run_command, diff --git a/core/db/models/project_state.py b/core/db/models/project_state.py index df9506d7..e415a931 100644 --- a/core/db/models/project_state.py +++ b/core/db/models/project_state.py @@ -168,8 +168,11 @@ class ProjectState(Base): :return: List of tuples with file path and content. """ - relevant = self.relevant_files or [] - return [file for file in self.files if file.path in relevant] + relevant_files = self.relevant_files or [] + modified_files = self.modified_files or {} + + all_files = set(relevant_files + list(modified_files.keys())) + return [file for file in self.files if file.path in all_files] @staticmethod def create_initial_state(branch: "Branch") -> "ProjectState": @@ -386,3 +389,29 @@ class ProjectState(Base): ProjectState.step_index > self.step_index, ) ) + + def get_last_iteration_steps(self) -> list: + """ + Get the steps of the last iteration. + + :return: A list of steps. + """ + return [s for s in self.steps if s.get("iteration_index") == len(self.iterations)] or self.steps + + def get_source_index(self, source: str) -> int: + """ + Get the index of the source which can be one of: 'app', 'feature', 'troubleshooting', 'review'. For example, + for feature return value would be number of current feature. + + :param source: The source to search for. + :return: The index of the source. + """ + if source in ["app", "feature"]: + return len([epic for epic in self.epics if epic.get("source") == source]) + elif source == "troubleshooting": + return len(self.iterations) + elif source == "review": + steps = self.get_last_iteration_steps() + return len([step for step in steps if step.get("type") == "review_task"]) + + return 1 diff --git a/core/state/state_manager.py b/core/state/state_manager.py index 4d194fcd..bcdc405d 100644 --- a/core/state/state_manager.py +++ b/core/state/state_manager.py @@ -178,12 +178,15 @@ class StateManager: ) if self.current_state.current_epic and self.current_state.current_task and self.ui: + source = self.current_state.current_epic.get("source", "app") await self.ui.send_task_progress( self.current_state.tasks.index(self.current_state.current_task) + 1, len(self.current_state.tasks), self.current_state.current_task["description"], - self.current_state.current_epic.get("source", "app"), + source, "in-progress", + self.current_state.get_source_index(source), + self.current_state.tasks, ) return self.current_state diff --git a/core/ui/base.py b/core/ui/base.py index f991fff6..404138f1 100644 --- a/core/ui/base.py +++ b/core/ui/base.py @@ -169,6 +169,7 @@ class UIBase: source: str, status: str, source_index: int = 1, + tasks: list[dict] = None, ): """ Send a task progress update to the UI. @@ -179,6 +180,7 @@ class UIBase: :param source: Source of the task, one of: 'app', 'feature', 'debugger', 'troubleshooting', 'review'. :param status: Status of the task, can be 'in_progress' or 'done'. :param source_index: Index of the source. + :param tasks: List of all tasks. """ raise NotImplementedError() diff --git a/core/ui/console.py b/core/ui/console.py index 4e10916a..cf114bab 100644 --- a/core/ui/console.py +++ b/core/ui/console.py @@ -89,6 +89,7 @@ class PlainConsoleUI(UIBase): source: str, status: str, source_index: int = 1, + tasks: list[dict] = None, ): pass diff --git a/core/ui/ipc_client.py b/core/ui/ipc_client.py index 9ffd0d71..438eca3a 100644 --- a/core/ui/ipc_client.py +++ b/core/ui/ipc_client.py @@ -262,6 +262,7 @@ class IPCClientUI(UIBase): source: str, status: str, source_index: int = 1, + tasks: list[dict] = None, ): await self._send( MessageType.PROGRESS, @@ -273,7 +274,8 @@ class IPCClientUI(UIBase): "source": source, "status": status, "source_index": source_index, - } + }, + "all_tasks": tasks, }, )