Spaces:
Running
Running
| """ | |
| μμ ν μμ€ν | |
| μμ΄μ νΈ κ° νμΌ μ κ·Ό μΆ©λμ λ°©μ§νκ³ μμ μμλ₯Ό μλμΌλ‘ μ‘°μ¨ | |
| """ | |
| import json | |
| from datetime import datetime, timedelta | |
| from typing import Dict, List, Optional | |
| from pathlib import Path | |
| from dataclasses import dataclass, asdict | |
| from enum import Enum | |
| from fnmatch import fnmatch | |
| class TaskStatus(Enum): | |
| """μμ μν""" | |
| PENDING = "pending" # λκΈ° μ€ | |
| QUEUED = "queued" # νμ λ±λ‘λ¨ | |
| IN_PROGRESS = "in_progress" # μ§ν μ€ | |
| COMPLETED = "completed" # μλ£ | |
| FAILED = "failed" # μ€ν¨ | |
| PAUSED = "paused" # μΌμ μ€μ§ | |
| class QueuedTask: | |
| """νμ λ±λ‘λ μμ """ | |
| task_id: str | |
| command: str | |
| agent: str | |
| status: TaskStatus | |
| required_files: List[str] | |
| locked_files: List[str] | |
| depends_on: Optional[str] | |
| created_at: str | |
| estimated_completion: Optional[str] | |
| progress: int = 0 | |
| def to_dict(self) -> Dict: | |
| return { | |
| **asdict(self), | |
| 'status': self.status.value | |
| } | |
| class WorkQueueManager: | |
| """μμ ν κ΄λ¦¬μ""" | |
| def __init__(self, queue_dir: str = None, config_path: str = None): | |
| import os | |
| if queue_dir is None: | |
| queue_dir = os.getenv("WORK_QUEUE_DIR", "/tmp/work-queue") | |
| if config_path is None: | |
| config_path = os.getenv("WORK_QUEUE_CONFIG", "/tmp/work-queue/config.json") | |
| self.queue_dir = Path(queue_dir) | |
| self.config_path = Path(config_path) | |
| self.queue_dir.mkdir(parents=True, exist_ok=True) | |
| self.config = self._load_config() | |
| self.tasks: Dict[str, QueuedTask] = {} | |
| self._load_tasks() | |
| def _load_config(self) -> Dict: | |
| """μ€μ λ‘λ""" | |
| if self.config_path.exists(): | |
| with open(self.config_path, 'r', encoding='utf-8') as f: | |
| return json.load(f) | |
| return {} | |
| def _load_tasks(self): | |
| """μ μ₯λ μμ λ‘λ""" | |
| # μ¬κΈ°μλ λ©λͺ¨λ¦¬μλ§ μ μ§ (νλ‘λμ μμλ DB μ¬μ© κΆμ₯) | |
| self.tasks = {} | |
| def enqueue_task( | |
| self, | |
| command: str, | |
| agent: str, | |
| required_files: List[str], | |
| depends_on: Optional[str] = None | |
| ) -> str: | |
| """μμ μ νμ μΆκ°""" | |
| task_id = f"task_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{agent}" | |
| # μμ‘΄μ± νμΈ | |
| if depends_on: | |
| if depends_on not in self.tasks: | |
| raise ValueError(f"Task {depends_on} not found") | |
| if self.tasks[depends_on].status != TaskStatus.COMPLETED: | |
| status = TaskStatus.QUEUED | |
| else: | |
| status = TaskStatus.PENDING | |
| else: | |
| status = TaskStatus.PENDING | |
| # νμΌ μ κΈ νμΈ | |
| locked_files = self._check_file_conflicts(required_files, agent) | |
| task = QueuedTask( | |
| task_id=task_id, | |
| command=command, | |
| agent=agent, | |
| status=status, | |
| required_files=required_files, | |
| locked_files=locked_files, | |
| depends_on=depends_on, | |
| created_at=datetime.now().isoformat() + "Z", | |
| estimated_completion=None, | |
| progress=0 | |
| ) | |
| self.tasks[task_id] = task | |
| self._save_queue_status() | |
| return task_id | |
| def _check_file_conflicts(self, required_files: List[str], agent: str) -> List[str]: | |
| """νμΌ μΆ©λ νμΈ""" | |
| locked_files = [] | |
| for file in required_files: | |
| # νμ¬ μ°κΈ° κΆνμ κ°μ§ λ€λ₯Έ μμ΄μ νΈ νμΈ | |
| for task_id, task in self.tasks.items(): | |
| if task.status == TaskStatus.IN_PROGRESS and task.agent != agent: | |
| if self._files_conflict(file, task.required_files): | |
| locked_files.append(file) | |
| break | |
| return locked_files | |
| def _files_conflict(self, file1: str, files2: List[str]) -> bool: | |
| """λ νμΌ λͺ©λ‘μ΄ μΆ©λνλμ§ νμΈ""" | |
| for file2 in files2: | |
| if fnmatch(file1, file2) or fnmatch(file2, file1): | |
| return True | |
| return False | |
| def start_task(self, task_id: str) -> bool: | |
| """μμ μμ""" | |
| if task_id not in self.tasks: | |
| return False | |
| task = self.tasks[task_id] | |
| # μμ‘΄μ± νμΈ | |
| if task.depends_on: | |
| if self.tasks[task.depends_on].status != TaskStatus.COMPLETED: | |
| return False | |
| # νμΌ μ κΈ λ€μ νμΈ | |
| if task.locked_files: | |
| return False | |
| task.status = TaskStatus.IN_PROGRESS | |
| task.estimated_completion = ( | |
| datetime.now() + timedelta(minutes=30) | |
| ).isoformat() + "Z" | |
| self._save_queue_status() | |
| return True | |
| def complete_task(self, task_id: str, progress: int = 100) -> bool: | |
| """μμ μλ£""" | |
| if task_id not in self.tasks: | |
| return False | |
| task = self.tasks[task_id] | |
| task.status = TaskStatus.COMPLETED | |
| task.progress = 100 | |
| # λ€μ λκΈ° μ€μΈ μμ μμ κ°λ₯νλλ‘ μ€μ | |
| self._trigger_dependent_tasks(task_id) | |
| self._save_queue_status() | |
| return True | |
| def fail_task(self, task_id: str, reason: str) -> bool: | |
| """μμ μ€ν¨ μ²λ¦¬""" | |
| if task_id not in self.tasks: | |
| return False | |
| task = self.tasks[task_id] | |
| task.status = TaskStatus.FAILED | |
| self._save_queue_status() | |
| return True | |
| def pause_task(self, task_id: str, reason: str) -> bool: | |
| """μμ μΌμ μ€μ§""" | |
| if task_id not in self.tasks: | |
| return False | |
| task = self.tasks[task_id] | |
| task.status = TaskStatus.PAUSED | |
| self._save_queue_status() | |
| return True | |
| def update_progress(self, task_id: str, progress: int) -> bool: | |
| """μμ μ§νλ₯ μ λ°μ΄νΈ""" | |
| if task_id not in self.tasks: | |
| return False | |
| task = self.tasks[task_id] | |
| task.progress = min(100, max(0, progress)) | |
| self._save_queue_status() | |
| return True | |
| def _trigger_dependent_tasks(self, completed_task_id: str): | |
| """μλ£λ μμ μ μμ‘΄νλ μμ νΈλ¦¬κ±°""" | |
| for task in self.tasks.values(): | |
| if task.depends_on == completed_task_id and task.status == TaskStatus.QUEUED: | |
| task.status = TaskStatus.PENDING | |
| # νμΌ μ κΈ λ€μ νμΈ | |
| task.locked_files = self._check_file_conflicts(task.required_files, task.agent) | |
| def get_next_runnable_task(self) -> Optional[QueuedTask]: | |
| """μ€ν κ°λ₯ν λ€μ μμ λ°ν""" | |
| # μ°μ μμ: μμ‘΄μ± μλ μμ > μμ‘΄μ± μλ£λ μμ | |
| pending_tasks = [t for t in self.tasks.values() if t.status == TaskStatus.PENDING] | |
| if not pending_tasks: | |
| return None | |
| # μΆ©λ μλ μμ μ°μ | |
| for task in pending_tasks: | |
| if not task.locked_files: | |
| return task | |
| return pending_tasks[0] if pending_tasks else None | |
| def get_queue_status(self) -> Dict: | |
| """νμ¬ ν μν μ‘°ν""" | |
| return { | |
| "total_tasks": len(self.tasks), | |
| "in_progress": len([t for t in self.tasks.values() if t.status == TaskStatus.IN_PROGRESS]), | |
| "queued": len([t for t in self.tasks.values() if t.status == TaskStatus.QUEUED]), | |
| "pending": len([t for t in self.tasks.values() if t.status == TaskStatus.PENDING]), | |
| "completed": len([t for t in self.tasks.values() if t.status == TaskStatus.COMPLETED]), | |
| "failed": len([t for t in self.tasks.values() if t.status == TaskStatus.FAILED]), | |
| "tasks": {task_id: task.to_dict() for task_id, task in self.tasks.items()} | |
| } | |
| def get_task(self, task_id: str) -> Optional[QueuedTask]: | |
| """μμ μ 보 μ‘°ν""" | |
| return self.tasks.get(task_id) | |
| def list_tasks(self, status: Optional[TaskStatus] = None, agent: Optional[str] = None) -> List[QueuedTask]: | |
| """μμ λͺ©λ‘ μ‘°ν""" | |
| tasks = list(self.tasks.values()) | |
| if status: | |
| tasks = [t for t in tasks if t.status == status] | |
| if agent: | |
| tasks = [t for t in tasks if t.agent == agent] | |
| return sorted(tasks, key=lambda t: t.created_at) | |
| def _save_queue_status(self): | |
| """ν μνλ₯Ό work-queue.mdλ‘ μ μ₯""" | |
| queue_file = self.queue_dir / "work-queue.md" | |
| # μνλ³λ‘ μμ λΆλ₯ | |
| in_progress_tasks = [t for t in self.tasks.values() if t.status == TaskStatus.IN_PROGRESS] | |
| queued_tasks = [t for t in self.tasks.values() if t.status == TaskStatus.QUEUED] | |
| pending_tasks = [t for t in self.tasks.values() if t.status == TaskStatus.PENDING] | |
| # Markdown μμ± | |
| md_content = f"""# π νμ± μμ ν | |
| **μμ±μΌ**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | |
| **λ§μ§λ§ μ λ°μ΄νΈ**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | |
| ## π΄ μ§ν μ€ (νμ¬ μ κΈ) | |
| """ | |
| if in_progress_tasks: | |
| for idx, task in enumerate(in_progress_tasks, 1): | |
| md_content += f"""### [{idx}] {task.agent} - {task.command} (in_progress) | |
| - **μμ **: {task.command} | |
| - **λμ νμΌ**: {', '.join([f'`{f}`' for f in task.required_files])} | |
| - **μμ**: {task.created_at} | |
| - **μ§νλ**: {task.progress}% | |
| - **μ κΈ μν**: {', '.join([f'β {f}' for f in task.required_files])} | |
| --- | |
| """ | |
| else: | |
| md_content += "νμ¬ μ§ν μ€μΈ μμ μμ\n\n---\n\n" | |
| md_content += """## π λκΈ° μ€ (μμ½λ¨) | |
| """ | |
| if queued_tasks: | |
| for idx, task in enumerate(queued_tasks, 1): | |
| md_content += f"""### [{idx}] {task.agent} - {task.command} (queued) | |
| - **μμ **: {task.command} | |
| - **λμ νμΌ**: {', '.join([f'`{f}`' for f in task.required_files])} | |
| - **μ ν 쑰건**: {task.depends_on or 'None'} | |
| - **μ κΈ λκΈ° νμΌ**: {', '.join(task.locked_files) if task.locked_files else 'None'} | |
| --- | |
| """ | |
| else: | |
| md_content += "λκΈ° μ€μΈ μμ μμ\n\n---\n\n" | |
| md_content += """## π‘ μ€λΉ μ€ | |
| """ | |
| if pending_tasks: | |
| for idx, task in enumerate(pending_tasks, 1): | |
| md_content += f"""### [{idx}] {task.agent} - {task.command} (pending) | |
| - **μμ **: {task.command} | |
| - **λμ νμΌ**: {', '.join([f'`{f}`' for f in task.required_files])} | |
| - **μ ν 쑰건**: {task.depends_on or 'None'} | |
| --- | |
| """ | |
| else: | |
| md_content += "μ€λΉ μ€μΈ μμ μμ\n\n" | |
| # νμΌ μ μ₯ | |
| with open(queue_file, 'w', encoding='utf-8') as f: | |
| f.write(md_content) | |
| def cleanup_old_tasks(self, days: int = 7) -> int: | |
| """μ€λλ μμ μ 리""" | |
| from datetime import timezone | |
| # timezone-aware cutoff_date μ¬μ© | |
| cutoff_date = datetime.now(timezone.utc) - timedelta(days=days) | |
| tasks_to_remove = [] | |
| for task_id, task in self.tasks.items(): | |
| created = datetime.fromisoformat(task.created_at.replace("Z", "+00:00")) | |
| if task.status in [TaskStatus.COMPLETED, TaskStatus.FAILED] and created < cutoff_date: | |
| tasks_to_remove.append(task_id) | |
| for task_id in tasks_to_remove: | |
| del self.tasks[task_id] | |
| if tasks_to_remove: | |
| self._save_queue_status() | |
| return len(tasks_to_remove) | |