fix(controller): Clean up stray pass in _run_event_loop (Task 5.5)
This commit is contained in:
@@ -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
|
||||||
@@ -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:
|
||||||
|
|||||||
329
src/models.py
329
src/models.py
@@ -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,
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user