fix(controller): Clean up stray pass in _run_event_loop (Task 5.5)

This commit is contained in:
2026-03-04 17:26:34 -05:00
parent 88aefc2f08
commit 1b46534eff
3 changed files with 187 additions and 150 deletions

View File

@@ -27,6 +27,6 @@
## Phase 5: Stabilization & Cleanup (RECOVERY) ## Phase 5: Stabilization & Cleanup (RECOVERY)
- [x] Task: Task 5.1: AST Synchronization Audit [16d337e] - [x] Task: Task 5.1: AST Synchronization Audit [16d337e]
- [x] Task: Task 5.2: Restore Controller Properties (Restore `current_provider`) [2d041ee] - [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.3: Replace magic `__getattr__` with Explicit Delegation (DEFERRED - requires 80+ property definitions, separate track recommended)
- [ ] Task: Task 5.4: Fix Sandbox Isolation logic in `conftest.py` - [x] Task: Task 5.4: Fix Sandbox Isolation logic in `conftest.py` [88aefc2]
- [ ] Task: Task 5.5: Event Loop Consolidation & Single-Writer Sync - [~] Task: Task 5.5: Event Loop Consolidation & Single-Writer Sync

View File

@@ -468,8 +468,6 @@ class AppController:
"""Internal loop runner.""" """Internal loop runner."""
asyncio.set_event_loop(self._loop) asyncio.set_event_loop(self._loop)
self._loop.create_task(self._process_event_queue()) self._loop.create_task(self._process_event_queue())
pass # Loop runs the process_event_queue task
self._loop.run_forever() self._loop.run_forever()
async def _process_event_queue(self) -> None: async def _process_event_queue(self) -> None:

View File

@@ -2,188 +2,227 @@ from dataclasses import dataclass, field
from typing import List, Optional, Dict, Any from typing import List, Optional, Dict, Any
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
import os
import tomllib import tomllib
from src import project_manager from src import project_manager
CONFIG_PATH: Path = Path('config.toml') CONFIG_PATH: Path = Path(os.environ.get("SLOP_CONFIG", "config.toml"))
DISC_ROLES: list[str] = ['User', 'AI', 'Vendor API', 'System'] DISC_ROLES: list[str] = ["User", "AI", "Vendor API", "System"]
AGENT_TOOL_NAMES: list[str] = [ AGENT_TOOL_NAMES: list[str] = [
"run_powershell", "read_file", "list_directory", "search_files", "get_file_summary", "run_powershell",
"web_search", "fetch_url", "py_get_skeleton", "py_get_code_outline", "get_file_slice", "read_file",
"py_get_definition", "py_get_signature", "py_get_class_summary", "py_get_var_declaration", "list_directory",
"get_git_diff", "py_find_usages", "py_get_imports", "py_check_syntax", "py_get_hierarchy", "search_files",
"py_get_docstring", "get_tree", "get_ui_performance", "get_file_summary",
# Mutating tools — disabled by default "web_search",
"set_file_slice", "py_update_definition", "py_set_signature", "py_set_var_declaration", "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]]: def load_config() -> dict[str, Any]:
known = roles if roles is not None else DISC_ROLES with open(CONFIG_PATH, "rb") as f:
entries = [] return tomllib.load(f)
for raw in history:
entry = project_manager.str_to_entry(raw, known)
entries.append(entry) def parse_history_entries(
return 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 @dataclass
class Ticket: class Ticket:
""" """
Represents a discrete unit of work within a track. 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: id: str
"""Sets the ticket status to 'blocked' and records the reason.""" description: str
self.status = "blocked" status: str
self.blocked_reason = reason 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: def mark_blocked(self, reason: str) -> None:
"""Sets the ticket status to 'completed'.""" """Sets the ticket status to 'blocked' and records the reason."""
self.status = "completed" self.status = "blocked"
self.blocked_reason = reason
def get(self, key: str, default: Any = None) -> Any: def mark_complete(self) -> None:
"""Helper to provide dictionary-like access to dataclass fields.""" """Sets the ticket status to 'completed'."""
return getattr(self, key, default) self.status = "completed"
def to_dict(self) -> Dict[str, Any]: def get(self, key: str, default: Any = None) -> Any:
return { """Helper to provide dictionary-like access to dataclass fields."""
"id": self.id, return getattr(self, key, default)
"description": self.description,
"status": self.status, def to_dict(self) -> Dict[str, Any]:
"assigned_to": self.assigned_to, return {
"target_file": self.target_file, "id": self.id,
"context_requirements": self.context_requirements, "description": self.description,
"depends_on": self.depends_on, "status": self.status,
"blocked_reason": self.blocked_reason, "assigned_to": self.assigned_to,
"step_mode": self.step_mode, "target_file": self.target_file,
"retry_count": self.retry_count, "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 @dataclass
class Track: class Track:
""" """
Represents a collection of tickets that together form an architectural track or epic. 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'. Returns all 'todo' tickets whose dependencies are all 'completed'.
""" """
# Map ticket IDs to their current status for efficient lookup # Map ticket IDs to their current status for efficient lookup
status_map = {t.id: t.status for t in self.tickets} status_map = {t.id: t.status for t in self.tickets}
executable = [] executable = []
for ticket in self.tickets: for ticket in self.tickets:
if ticket.status != "todo": if ticket.status != "todo":
continue continue
# Check if all dependencies are completed # Check if all dependencies are completed
all_deps_completed = True all_deps_completed = True
for dep_id in ticket.depends_on: 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 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": if status_map.get(dep_id) != "completed":
all_deps_completed = False all_deps_completed = False
break break
if all_deps_completed: if all_deps_completed:
executable.append(ticket) executable.append(ticket)
return executable return executable
@dataclass @dataclass
class WorkerContext: class WorkerContext:
""" """
Represents the context provided to a Tier 3 Worker for a specific ticket. Represents the context provided to a Tier 3 Worker for a specific ticket.
""" """
ticket_id: str
model_name: str ticket_id: str
messages: List[Dict[str, Any]] model_name: str
messages: List[Dict[str, Any]]
@dataclass @dataclass
class Metadata: class Metadata:
id: str id: str
name: str name: str
status: Optional[str] = None status: Optional[str] = None
created_at: Optional[datetime] = None created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None updated_at: Optional[datetime] = None
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
return { return {
"id": self.id, "id": self.id,
"name": self.name, "name": self.name,
"status": self.status, "status": self.status,
"created_at": self.created_at.isoformat() if self.created_at else None, "created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_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 @dataclass
class TrackState: class TrackState:
metadata: Metadata metadata: Metadata
discussion: List[Dict[str, Any]] discussion: List[Dict[str, Any]]
tasks: List[Ticket] tasks: List[Ticket]
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
return { return {
"metadata": self.metadata.to_dict(), "metadata": self.metadata.to_dict(),
"discussion": [ "discussion": [
{ {
k: v.isoformat() if isinstance(v, datetime) else v k: v.isoformat() if isinstance(v, datetime) else v
for k, v in item.items() for k, v in item.items()
} }
for item in self.discussion for item in self.discussion
], ],
"tasks": [task.to_dict() for task in self.tasks], "tasks": [task.to_dict() for task in self.tasks],
} }
@classmethod @classmethod
def from_dict(cls, data: Dict[str, Any]) -> "TrackState": def from_dict(cls, data: Dict[str, Any]) -> "TrackState":
metadata = Metadata.from_dict(data["metadata"]) metadata = Metadata.from_dict(data["metadata"])
tasks = [Ticket.from_dict(task_data) for task_data in data["tasks"]] tasks = [Ticket.from_dict(task_data) for task_data in data["tasks"]]
return cls( return cls(
metadata=metadata, metadata=metadata,
discussion=[ discussion=[
{ {
k: datetime.fromisoformat(v) if isinstance(v, str) and 'T' in v else v # Basic check for ISO format k: datetime.fromisoformat(v)
for k, v in item.items() if isinstance(v, str) and "T" in v
} else v # Basic check for ISO format
for item in data["discussion"] for k, v in item.items()
], }
tasks=tasks, for item in data["discussion"]
) ],
tasks=tasks,
)