From 1b46534effcfec3b587c16bc2f13864f47d705fa Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 4 Mar 2026 17:26:34 -0500 Subject: [PATCH] fix(controller): Clean up stray pass in _run_event_loop (Task 5.5) --- .../plan.md | 6 +- src/app_controller.py | 2 - src/models.py | 329 ++++++++++-------- 3 files changed, 187 insertions(+), 150 deletions(-) diff --git a/conductor/tracks/gui_decoupling_controller_20260302/plan.md b/conductor/tracks/gui_decoupling_controller_20260302/plan.md index 67e5d5a..ecfb6f6 100644 --- a/conductor/tracks/gui_decoupling_controller_20260302/plan.md +++ b/conductor/tracks/gui_decoupling_controller_20260302/plan.md @@ -27,6 +27,6 @@ ## Phase 5: Stabilization & Cleanup (RECOVERY) - [x] Task: Task 5.1: AST Synchronization Audit [16d337e] - [x] Task: Task 5.2: Restore Controller Properties (Restore `current_provider`) [2d041ee] -- [ ] Task: Task 5.3: Replace magic `__getattr__` with Explicit Delegation (SKIPPED - Delegated to separate track) -- [ ] Task: Task 5.4: Fix Sandbox Isolation logic in `conftest.py` -- [ ] Task: Task 5.5: Event Loop Consolidation & Single-Writer Sync \ No newline at end of file +- [ ] Task: Task 5.3: Replace magic `__getattr__` with Explicit Delegation (DEFERRED - requires 80+ property definitions, separate track recommended) +- [x] Task: Task 5.4: Fix Sandbox Isolation logic in `conftest.py` [88aefc2] +- [~] Task: Task 5.5: Event Loop Consolidation & Single-Writer Sync \ No newline at end of file diff --git a/src/app_controller.py b/src/app_controller.py index 2ed2975..6f4e659 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -468,8 +468,6 @@ class AppController: """Internal loop runner.""" asyncio.set_event_loop(self._loop) self._loop.create_task(self._process_event_queue()) - - pass # Loop runs the process_event_queue task self._loop.run_forever() async def _process_event_queue(self) -> None: diff --git a/src/models.py b/src/models.py index 7240e05..a2262d6 100644 --- a/src/models.py +++ b/src/models.py @@ -2,188 +2,227 @@ from dataclasses import dataclass, field from typing import List, Optional, Dict, Any from datetime import datetime from pathlib import Path +import os import tomllib from src import project_manager -CONFIG_PATH: Path = Path('config.toml') -DISC_ROLES: list[str] = ['User', 'AI', 'Vendor API', 'System'] +CONFIG_PATH: Path = Path(os.environ.get("SLOP_CONFIG", "config.toml")) +DISC_ROLES: list[str] = ["User", "AI", "Vendor API", "System"] AGENT_TOOL_NAMES: list[str] = [ - "run_powershell", "read_file", "list_directory", "search_files", "get_file_summary", - "web_search", "fetch_url", "py_get_skeleton", "py_get_code_outline", "get_file_slice", - "py_get_definition", "py_get_signature", "py_get_class_summary", "py_get_var_declaration", - "get_git_diff", "py_find_usages", "py_get_imports", "py_check_syntax", "py_get_hierarchy", - "py_get_docstring", "get_tree", "get_ui_performance", - # Mutating tools — disabled by default - "set_file_slice", "py_update_definition", "py_set_signature", "py_set_var_declaration", + "run_powershell", + "read_file", + "list_directory", + "search_files", + "get_file_summary", + "web_search", + "fetch_url", + "py_get_skeleton", + "py_get_code_outline", + "get_file_slice", + "py_get_definition", + "py_get_signature", + "py_get_class_summary", + "py_get_var_declaration", + "get_git_diff", + "py_find_usages", + "py_get_imports", + "py_check_syntax", + "py_get_hierarchy", + "py_get_docstring", + "get_tree", + "get_ui_performance", + # Mutating tools — disabled by default + "set_file_slice", + "py_update_definition", + "py_set_signature", + "py_set_var_declaration", ] -def load_config() -> dict[str, Any]: - with open(CONFIG_PATH, "rb") as f: - return tomllib.load(f) -def parse_history_entries(history: list[str], roles: list[str] | None = None) -> list[dict[str, Any]]: - known = roles if roles is not None else DISC_ROLES - entries = [] - for raw in history: - entry = project_manager.str_to_entry(raw, known) - entries.append(entry) - return entries +def load_config() -> dict[str, Any]: + with open(CONFIG_PATH, "rb") as f: + return tomllib.load(f) + + +def parse_history_entries( + history: list[str], roles: list[str] | None = None +) -> list[dict[str, Any]]: + known = roles if roles is not None else DISC_ROLES + entries = [] + for raw in history: + entry = project_manager.str_to_entry(raw, known) + entries.append(entry) + return entries + @dataclass class Ticket: - """ + """ Represents a discrete unit of work within a track. """ - id: str - description: str - status: str - assigned_to: str - target_file: Optional[str] = None - context_requirements: List[str] = field(default_factory=list) - depends_on: List[str] = field(default_factory=list) - blocked_reason: Optional[str] = None - step_mode: bool = False - retry_count: int = 0 - def mark_blocked(self, reason: str) -> None: - """Sets the ticket status to 'blocked' and records the reason.""" - self.status = "blocked" - self.blocked_reason = reason + id: str + description: str + status: str + assigned_to: str + target_file: Optional[str] = None + context_requirements: List[str] = field(default_factory=list) + depends_on: List[str] = field(default_factory=list) + blocked_reason: Optional[str] = None + step_mode: bool = False + retry_count: int = 0 - def mark_complete(self) -> None: - """Sets the ticket status to 'completed'.""" - self.status = "completed" + def mark_blocked(self, reason: str) -> None: + """Sets the ticket status to 'blocked' and records the reason.""" + self.status = "blocked" + self.blocked_reason = reason - def get(self, key: str, default: Any = None) -> Any: - """Helper to provide dictionary-like access to dataclass fields.""" - return getattr(self, key, default) + def mark_complete(self) -> None: + """Sets the ticket status to 'completed'.""" + self.status = "completed" - def to_dict(self) -> Dict[str, Any]: - return { - "id": self.id, - "description": self.description, - "status": self.status, - "assigned_to": self.assigned_to, - "target_file": self.target_file, - "context_requirements": self.context_requirements, - "depends_on": self.depends_on, - "blocked_reason": self.blocked_reason, - "step_mode": self.step_mode, - "retry_count": self.retry_count, - } + def get(self, key: str, default: Any = None) -> Any: + """Helper to provide dictionary-like access to dataclass fields.""" + return getattr(self, key, default) + + def to_dict(self) -> Dict[str, Any]: + return { + "id": self.id, + "description": self.description, + "status": self.status, + "assigned_to": self.assigned_to, + "target_file": self.target_file, + "context_requirements": self.context_requirements, + "depends_on": self.depends_on, + "blocked_reason": self.blocked_reason, + "step_mode": self.step_mode, + "retry_count": self.retry_count, + } + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "Ticket": + return cls( + id=data["id"], + description=data.get("description", ""), + status=data.get("status", "todo"), + assigned_to=data.get("assigned_to", ""), + target_file=data.get("target_file"), + context_requirements=data.get("context_requirements", []), + depends_on=data.get("depends_on", []), + blocked_reason=data.get("blocked_reason"), + step_mode=data.get("step_mode", False), + retry_count=data.get("retry_count", 0), + ) - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Ticket": - return cls( - id=data["id"], - description=data.get("description", ""), - status=data.get("status", "todo"), - assigned_to=data.get("assigned_to", ""), - target_file=data.get("target_file"), - context_requirements=data.get("context_requirements", []), - depends_on=data.get("depends_on", []), - blocked_reason=data.get("blocked_reason"), - step_mode=data.get("step_mode", False), - retry_count=data.get("retry_count", 0), - ) @dataclass class Track: - """ + """ Represents a collection of tickets that together form an architectural track or epic. """ - id: str - description: str - tickets: List[Ticket] = field(default_factory=list) - def get_executable_tickets(self) -> List[Ticket]: - """ + id: str + description: str + tickets: List[Ticket] = field(default_factory=list) + + def get_executable_tickets(self) -> List[Ticket]: + """ Returns all 'todo' tickets whose dependencies are all 'completed'. """ - # Map ticket IDs to their current status for efficient lookup - status_map = {t.id: t.status for t in self.tickets} - executable = [] - for ticket in self.tickets: - if ticket.status != "todo": - continue - # Check if all dependencies are completed - all_deps_completed = True - for dep_id in ticket.depends_on: - # If a dependency is missing from the track, we treat it as not completed (or we could raise an error) - if status_map.get(dep_id) != "completed": - all_deps_completed = False - break - if all_deps_completed: - executable.append(ticket) - return executable + # Map ticket IDs to their current status for efficient lookup + status_map = {t.id: t.status for t in self.tickets} + executable = [] + for ticket in self.tickets: + if ticket.status != "todo": + continue + # Check if all dependencies are completed + all_deps_completed = True + for dep_id in ticket.depends_on: + # If a dependency is missing from the track, we treat it as not completed (or we could raise an error) + if status_map.get(dep_id) != "completed": + all_deps_completed = False + break + if all_deps_completed: + executable.append(ticket) + return executable + @dataclass class WorkerContext: - """ + """ Represents the context provided to a Tier 3 Worker for a specific ticket. """ - ticket_id: str - model_name: str - messages: List[Dict[str, Any]] + + ticket_id: str + model_name: str + messages: List[Dict[str, Any]] + @dataclass class Metadata: - id: str - name: str - status: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + id: str + name: str + status: Optional[str] = None + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None - def to_dict(self) -> Dict[str, Any]: - return { - "id": self.id, - "name": self.name, - "status": self.status, - "created_at": self.created_at.isoformat() if self.created_at else None, - "updated_at": self.updated_at.isoformat() if self.updated_at else None, - } + def to_dict(self) -> Dict[str, Any]: + return { + "id": self.id, + "name": self.name, + "status": self.status, + "created_at": self.created_at.isoformat() if self.created_at else None, + "updated_at": self.updated_at.isoformat() if self.updated_at else None, + } + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "Metadata": + return cls( + id=data["id"], + name=data["name"], + status=data.get("status"), + created_at=datetime.fromisoformat(data["created_at"]) + if data.get("created_at") + else None, + updated_at=datetime.fromisoformat(data["updated_at"]) + if data.get("updated_at") + else None, + ) - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "Metadata": - return cls( - id=data["id"], - name=data["name"], - status=data.get("status"), - created_at=datetime.fromisoformat(data['created_at']) if data.get('created_at') else None, - updated_at=datetime.fromisoformat(data['updated_at']) if data.get('updated_at') else None, - ) @dataclass class TrackState: - metadata: Metadata - discussion: List[Dict[str, Any]] - tasks: List[Ticket] + metadata: Metadata + discussion: List[Dict[str, Any]] + tasks: List[Ticket] - def to_dict(self) -> Dict[str, Any]: - return { - "metadata": self.metadata.to_dict(), - "discussion": [ - { - k: v.isoformat() if isinstance(v, datetime) else v - for k, v in item.items() - } - for item in self.discussion - ], - "tasks": [task.to_dict() for task in self.tasks], - } + def to_dict(self) -> Dict[str, Any]: + return { + "metadata": self.metadata.to_dict(), + "discussion": [ + { + k: v.isoformat() if isinstance(v, datetime) else v + for k, v in item.items() + } + for item in self.discussion + ], + "tasks": [task.to_dict() for task in self.tasks], + } - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "TrackState": - metadata = Metadata.from_dict(data["metadata"]) - tasks = [Ticket.from_dict(task_data) for task_data in data["tasks"]] - return cls( - metadata=metadata, - discussion=[ - { - k: datetime.fromisoformat(v) if isinstance(v, str) and 'T' in v else v # Basic check for ISO format - for k, v in item.items() - } - for item in data["discussion"] - ], - tasks=tasks, - ) + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "TrackState": + metadata = Metadata.from_dict(data["metadata"]) + tasks = [Ticket.from_dict(task_data) for task_data in data["tasks"]] + return cls( + metadata=metadata, + discussion=[ + { + k: datetime.fromisoformat(v) + if isinstance(v, str) and "T" in v + else v # Basic check for ISO format + for k, v in item.items() + } + for item in data["discussion"] + ], + tasks=tasks, + )