182 lines
6.6 KiB
Python
182 lines
6.6 KiB
Python
"""
|
|
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,
|
|
}
|