""" Dynamic Workflow Engine for Foxy Dev Team. Replaces the static STATUS_TRANSITIONS dict from foxy-autopilot.py with a flexible engine that supports 4 workflow types and routes tasks between agents accordingly. """ from dataclasses import dataclass from typing import Optional from app.models import ProjectStatus, WorkflowType @dataclass(frozen=True) class WorkflowStep: """A single step in a workflow pipeline.""" agent_name: str # e.g. "Foxy-Conductor" awaiting_status: str # e.g. "AWAITING_CONDUCTOR" running_status: str # e.g. "CONDUCTOR_RUNNING" # ─── Workflow Definitions ────────────────────────────────────────────────────── # Workflow 1: Standard Software Design # Conductor → Architect → Dev → UIUX → QA → Admin SOFTWARE_DESIGN_STEPS = [ WorkflowStep("Foxy-Conductor", "AWAITING_CONDUCTOR", "CONDUCTOR_RUNNING"), WorkflowStep("Foxy-Architect", "AWAITING_ARCHITECT", "ARCHITECT_RUNNING"), WorkflowStep("Foxy-Dev", "AWAITING_DEV", "DEV_RUNNING"), WorkflowStep("Foxy-UIUX", "AWAITING_UIUX", "UIUX_RUNNING"), WorkflowStep("Foxy-QA", "AWAITING_QA", "QA_RUNNING"), WorkflowStep("Foxy-Admin", "AWAITING_DEPLOY", "DEPLOY_RUNNING"), ] # Workflow 2: Sysadmin Debug # Conductor → Admin → QA SYSADMIN_DEBUG_STEPS = [ WorkflowStep("Foxy-Conductor", "AWAITING_CONDUCTOR", "CONDUCTOR_RUNNING"), WorkflowStep("Foxy-Admin", "AWAITING_DEPLOY", "DEPLOY_RUNNING"), WorkflowStep("Foxy-QA", "AWAITING_QA", "QA_RUNNING"), ] # Workflow 3: DevOps Setup # Conductor → Admin → QA DEVOPS_SETUP_STEPS = [ WorkflowStep("Foxy-Conductor", "AWAITING_CONDUCTOR", "CONDUCTOR_RUNNING"), WorkflowStep("Foxy-Admin", "AWAITING_DEPLOY", "DEPLOY_RUNNING"), WorkflowStep("Foxy-QA", "AWAITING_QA", "QA_RUNNING"), ] # Workflow 4: Sysadmin Adjust # Conductor → Admin → QA SYSADMIN_ADJUST_STEPS = [ WorkflowStep("Foxy-Conductor", "AWAITING_CONDUCTOR", "CONDUCTOR_RUNNING"), WorkflowStep("Foxy-Admin", "AWAITING_DEPLOY", "DEPLOY_RUNNING"), WorkflowStep("Foxy-QA", "AWAITING_QA", "QA_RUNNING"), ] WORKFLOW_REGISTRY: dict[WorkflowType, list[WorkflowStep]] = { WorkflowType.SOFTWARE_DESIGN: SOFTWARE_DESIGN_STEPS, WorkflowType.SYSADMIN_DEBUG: SYSADMIN_DEBUG_STEPS, WorkflowType.DEVOPS_SETUP: DEVOPS_SETUP_STEPS, WorkflowType.SYSADMIN_ADJUST: SYSADMIN_ADJUST_STEPS, } # ─── Agent Label Mapping ────────────────────────────────────────────────────── AGENT_LABELS = { "Foxy-Conductor": "foxy-conductor", "Foxy-Architect": "foxy-architect", "Foxy-Dev": "foxy-dev", "Foxy-UIUX": "foxy-uiux", "Foxy-QA": "foxy-qa", "Foxy-Admin": "foxy-admin", } AGENT_DISPLAY_NAMES = {v: k for k, v in AGENT_LABELS.items()} AGENT_MODELS = { "Foxy-Conductor": "grok-4.1-fast", "Foxy-Architect": "grok-4.1-fast", "Foxy-Dev": "minimax-m2.5", "Foxy-UIUX": "qwen3-30b-a3b", "Foxy-QA": "qwen3.5-flash", "Foxy-Admin": "grok-4.1-fast", } # ─── Engine Functions ────────────────────────────────────────────────────────── def get_workflow_steps(workflow_type: WorkflowType) -> list[WorkflowStep]: """Get the ordered list of steps for a workflow type.""" return WORKFLOW_REGISTRY.get(workflow_type, SOFTWARE_DESIGN_STEPS) def get_current_step(workflow_type: WorkflowType, status: str) -> Optional[WorkflowStep]: """Find the step matching the current project status (awaiting or running).""" steps = get_workflow_steps(workflow_type) for step in steps: if status in (step.awaiting_status, step.running_status): return step return None def get_next_step(workflow_type: WorkflowType, status: str) -> Optional[WorkflowStep]: """ Given a 'RUNNING' status, determine the next awaiting step in the workflow. Returns None if the current step is the last one (project should be COMPLETED). """ steps = get_workflow_steps(workflow_type) for i, step in enumerate(steps): if status == step.running_status: if i + 1 < len(steps): return steps[i + 1] else: return None # Last step — project is complete return None def get_next_awaiting_status(workflow_type: WorkflowType, current_running_status: str) -> str: """ After a running agent finishes, what's the next status? Returns COMPLETED if the workflow is done. """ next_step = get_next_step(workflow_type, current_running_status) if next_step: return next_step.awaiting_status return "COMPLETED" def is_awaiting_status(status: str) -> bool: """Check if a status represents an 'awaiting' state.""" return status.startswith("AWAITING_") def is_running_status(status: str) -> bool: """Check if a status represents a 'running' state.""" return status.endswith("_RUNNING") def is_terminal_status(status: str) -> bool: """Check if a status is terminal (COMPLETED / FAILED).""" return status in ("COMPLETED", "FAILED") def get_workflow_progress(workflow_type: WorkflowType, status: str) -> dict: """ Calculate the progress of a project through its workflow. Returns {current_step, total_steps, percentage, completed_agents, remaining_agents}. """ steps = get_workflow_steps(workflow_type) total = len(steps) if is_terminal_status(status): return { "current_step": total if status == "COMPLETED" else 0, "total_steps": total, "percentage": 100 if status == "COMPLETED" else 0, "completed_agents": [s.agent_name for s in steps] if status == "COMPLETED" else [], "remaining_agents": [], } current_idx = 0 for i, step in enumerate(steps): if status in (step.awaiting_status, step.running_status): current_idx = i break completed = [s.agent_name for s in steps[:current_idx]] remaining = [s.agent_name for s in steps[current_idx:]] percentage = int((current_idx / total) * 100) if total > 0 else 0 return { "current_step": current_idx, "total_steps": total, "percentage": percentage, "completed_agents": completed, "remaining_agents": remaining, }