"""Main Agent routing between simple chat and internal Task mode.""" from __future__ import annotations import re from .models import MainAgentDecision, TaskRecord class MainAgentRouter: """Small deterministic classifier used before the main AgentLoop. The first version intentionally avoids a mandatory model call so the router stays reliable during provider outages. The rule set is conservative: anything that implies execution, files, tools, iteration, or validation becomes Task mode. """ _TASK_PATTERNS = [ r"\b(implement|fix|debug|refactor|migrate|build|create|write|edit|update|test|validate|deploy)\b", r"\b(file|repo|code|project|backend|frontend|api|database|migration|pull request|ci|bug)\b", r"\b(step|multi-step|workflow|plan and|then)\b", r"(实现|修复|调试|重构|迁移|构建|创建|编写|修改|更新|测试|验证|部署|文件|代码|项目|前端|后端|接口|数据库|多步|任务)", ] _NEW_TASK_PATTERNS = [ r"\b(new task|another task|different task|start over)\b", r"(新任务|另一个任务|换个任务|重新开始)", ] def classify(self, message: str, *, active_task: TaskRecord | None = None) -> MainAgentDecision: text = message.strip() lowered = text.lower() starts_new = any(re.search(pattern, lowered, re.IGNORECASE) for pattern in self._NEW_TASK_PATTERNS) if active_task is not None and active_task.status in {"awaiting_feedback", "needs_revision"} and not starts_new: return MainAgentDecision(mode="task", reason="continuing_open_task", starts_new_task=False) if any(re.search(pattern, lowered, re.IGNORECASE) for pattern in self._TASK_PATTERNS): return MainAgentDecision(mode="task", reason="task_pattern_matched", starts_new_task=starts_new) if len(text) > 240: return MainAgentDecision(mode="task", reason="long_request", starts_new_task=starts_new) return MainAgentDecision(mode="simple", reason="simple_question", starts_new_task=False)