diff --git a/src/antigravity_provider/router/auto_assigner.py b/src/antigravity_provider/router/auto_assigner.py index a9fde50..662e677 100644 --- a/src/antigravity_provider/router/auto_assigner.py +++ b/src/antigravity_provider/router/auto_assigner.py @@ -15,6 +15,7 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Tuple from antigravity_provider.router.profile_manager import ProfileAuthManager, mask_email, mask_id +from antigravity_provider.router.role_registry import RoleRegistry from antigravity_provider.router.router_config import ( RolePolicy, RouterConfig, @@ -25,19 +26,7 @@ from antigravity_provider.router.router_config import ( logger = logging.getLogger("hermes.router.auto_assigner") -HUMAN_ROLE_LABELS = { - "orchestrator_primary": "Главный оркестратор", - "orchestrator_fallback": "Резервный оркестратор", - "coder_1": "Кодер 1", - "coder_2": "Кодер 2", - "reviewer": "Ревьюер", - "researcher": "Исследователь", - "fast_agent": "Быстрый агент", - "universal_subagent": "Универсальный субагент", - "spare_1": "Резерв 1", - "spare_2": "Резерв 2", - "cold_spare": "Холодный резерв", -} +HUMAN_ROLE_LABELS = RoleRegistry.get_human_role_labels() DEFAULT_SLOT_ROLES = { "codex-orch": ("Главный оркестратор", "orchestrator", "primary"), @@ -67,35 +56,7 @@ DEFAULT_SLOT_ROLES = { } -CANONICAL_ROLE_MAP = { - "orchestrator": "orchestrator", - "orchestrator_primary": "orchestrator", - "orchestrator_fallback": "orchestrator", - "главный оркестратор": "orchestrator", - "резервный оркестратор": "orchestrator", - "coder": "coder-primary", - "coder_1": "coder-primary", - "coder-1": "coder-primary", - "coder-primary": "coder-primary", - "кодер 1": "coder-primary", - "coder_2": "coder-secondary", - "coder-2": "coder-secondary", - "coder-secondary": "coder-secondary", - "кодер 2": "coder-secondary", - "reviewer": "reviewer", - "ревьюер": "reviewer", - "research": "research", - "researcher": "research", - "исследователь": "research", - "fast": "fast", - "fast_agent": "fast", - "быстрый агент": "fast", - "general": "fast", - "universal_subagent": "fast", - "универсальный субагент": "fast", - "tester": "fast", - "тестировщик": "fast", -} +CANONICAL_ROLE_MAP = RoleRegistry.get_canonical_role_map() class AutoAssigner: @@ -172,7 +133,7 @@ class AutoAssigner: candidates = list(provider_slots.get(provider_norm, [])) # Priority based on requested role - if requested_role == "orchestrator": + if requested_role == "manager": if provider_norm in ("openai-codex", "codex") and "codex-orch" in candidates: candidates.remove("codex-orch") candidates.insert(0, "codex-orch") @@ -368,7 +329,7 @@ class AutoAssigner: "changes": [], } - canonical_roles = ["orchestrator", "coder-primary", "coder-secondary", "reviewer", "research", "fast"] + canonical_roles = [r for r in RoleRegistry.get_role_ids() if RoleRegistry.is_role_implemented(r)] changes: List[Dict[str, Any]] = [] # Ensure canonical roles exist in config @@ -418,12 +379,12 @@ class AutoAssigner: else: # Case 3: Multiple providers connected -> distribute by role provider preferences role_provider_preferences = { - "orchestrator": ["codex", "antigravity", "opencode", "claude", "grok", "local"], - "coder-primary": ["codex", "antigravity", "opencode", "claude", "grok", "local"], - "coder-secondary": ["codex", "antigravity", "opencode", "claude", "grok", "local"], - "reviewer": ["codex", "opencode", "antigravity", "claude", "grok", "local"], - "research": ["opencode", "antigravity", "grok", "claude", "codex", "local"], - "fast": ["opencode", "antigravity", "local", "grok", "codex", "claude"], + "manager": ["codex", "antigravity", "opencode", "claude", "grok", "local"], + "developer-1": ["codex", "antigravity", "opencode", "claude", "grok", "local"], + "developer-2": ["codex", "antigravity", "opencode", "claude", "grok", "local"], + "code-reviewer": ["codex", "opencode", "antigravity", "claude", "grok", "local"], + "researcher": ["opencode", "antigravity", "grok", "claude", "codex", "local"], + "tester": ["opencode", "antigravity", "local", "grok", "codex", "claude"], } by_prov: Dict[str, List[str]] = {} @@ -503,7 +464,7 @@ class AutoAssigner: main_codex = ProfileAuthManager.get_main_profile("openai-codex") team = { - "orchestrator": [], + "manager": [], "subagents": [], "spares": [], "summary": { diff --git a/src/antigravity_provider/router/role_registry.py b/src/antigravity_provider/router/role_registry.py new file mode 100644 index 0000000..b47230c --- /dev/null +++ b/src/antigravity_provider/router/role_registry.py @@ -0,0 +1,303 @@ +"""Hermes Multi-Provider Account Router — Unified Role Registry.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional, Tuple + + + +STATUS_DECLARED_UNIMPLEMENTED = "DECLARED_UNIMPLEMENTED" +STATUS_DECLARED_UNIMPLEMENTED_LABEL_RU = "Роль объявлена, исполнение не реализовано" + +@dataclass(frozen=True) +class RoleDefinition: + role_id: str + display_name_ru: str + short_name_ru: str + description_ru: str + is_implemented: bool = True + unimplemented_reason_ru: Optional[str] = None + capabilities: List[str] = field(default_factory=list) + fallback_capabilities: List[str] = field(default_factory=list) + default_preferred_chain: List[str] = field(default_factory=list) + default_model: Optional[str] = None + max_failover_attempts: int = 3 + session_affinity_enabled: bool = True + is_orchestrator: bool = False + tier: str = "core" + +CANONICAL_ROLES: Dict[str, RoleDefinition] = { + "manager": RoleDefinition( + role_id="manager", + display_name_ru="Менеджер проекта (Оркестратор)", + short_name_ru="Менеджер", + description_ru="Координация работы субагентов, планирование, контроль исполнения и распределение задач.", + is_implemented=True, + is_orchestrator=True, + capabilities=["orchestrator", "reasoning", "management", "planning"], + fallback_capabilities=["orchestrator", "reasoning"], + default_preferred_chain=["codex-orch", "ag-orch-fallback", "claude-orch", "grok-orch", "opengo-3"], + default_model="gemini-3.7-flash", + max_failover_attempts=3, + tier="core", + ), + "developer-1": RoleDefinition( + role_id="developer-1", + display_name_ru="Ведущий разработчик (Кодер 1)", + short_name_ru="Кодер 1", + description_ru="Пишет код, реализует функционал, исправляет ошибки.", + is_implemented=True, + capabilities=["coding", "developer-1", "reasoning", "tools", "structured_output"], + fallback_capabilities=["coding"], + default_preferred_chain=["codex-worker-1", "claude-worker-1", "ag-w1", "opengo-1"], + max_failover_attempts=3, + tier="core", + ), + "developer-2": RoleDefinition( + role_id="developer-2", + display_name_ru="Вспомогательный разработчик (Кодер 2)", + short_name_ru="Кодер 2", + description_ru="Проверяет код Разработчика 1 и выдаёт ему задание на исправление.", + is_implemented=True, + capabilities=["coding", "developer-2", "reviewer", "tools"], + fallback_capabilities=["coding", "reviewer"], + default_preferred_chain=["ag-w1", "grok-worker-1", "codex-worker-2", "opengo-3"], + max_failover_attempts=3, + tier="core", + ), + "code-reviewer": RoleDefinition( + role_id="code-reviewer", + display_name_ru="Ревьюер кода", + short_name_ru="Код-ревьювер", + description_ru="Анализирует код на ошибки, проблемы безопасности и соответствие стандартам. Работает после Разработчика 2.", + is_implemented=True, + capabilities=["code-reviewer", "reviewer", "coding", "security_analysis"], + fallback_capabilities=["reviewer", "coding"], + default_preferred_chain=["claude-worker-2", "codex-worker-2", "ag-w2", "opengo-2"], + max_failover_attempts=3, + tier="core", + ), + "researcher": RoleDefinition( + role_id="researcher", + display_name_ru="Исследователь", + short_name_ru="Исследователь", + description_ru="Изучает данные, кодовую базу, документацию и внешние источники, чтобы собрать информацию для решения задачи.", + is_implemented=True, + capabilities=["researcher", "research", "search", "long_context"], + fallback_capabilities=["research", "search"], + default_preferred_chain=["ag-w3", "grok-worker-2", "opengo-2", "opengo-1"], + max_failover_attempts=3, + tier="core", + ), + "tester": RoleDefinition( + role_id="tester", + display_name_ru="Тестировщик (QA)", + short_name_ru="Тестировщик", + description_ru="Создаёт тесты, проверяет корректность работы кода, находит дефекты.", + is_implemented=True, + capabilities=["tester", "testing", "fast", "automation"], + fallback_capabilities=["fast", "testing"], + default_preferred_chain=["ag-w4", "opengo-1", "ag-spare-1"], + max_failover_attempts=3, + tier="qa_doc", + ), + "tech-writer": RoleDefinition( + role_id="tech-writer", + display_name_ru="Технический писатель", + short_name_ru="Техписатель", + description_ru="Создаёт документацию, инструкции, README.", + is_implemented=True, + capabilities=["tech-writer", "documentation", "reasoning", "structured_output"], + fallback_capabilities=["documentation", "reasoning"], + default_preferred_chain=["claude-worker-2", "ag-w3", "ag-w2"], + max_failover_attempts=3, + tier="qa_doc", + ), + "analyst": RoleDefinition( + role_id="analyst", + display_name_ru="Системный аналитик", + short_name_ru="Аналитик", + description_ru="Проводит глубокий анализ данных, выявляет тренды, строит прогнозы.", + is_implemented=True, + capabilities=["analyst", "reasoning", "research", "planning"], + fallback_capabilities=["reasoning", "research"], + default_preferred_chain=["grok-worker-2", "opengo-2", "ag-w3"], + max_failover_attempts=3, + tier="qa_doc", + ), + "guardian": RoleDefinition( + role_id="guardian", + display_name_ru="Надзиратель (агент безопасности)", + short_name_ru="Надзиратель", + description_ru="Проверяет входящие инструкции на промпт-инъекции, анализирует планы и вызовы инструментов, блокирует обход системных правил, не допускает утечки секретов, следит за границами песочницы.", + is_implemented=False, + unimplemented_reason_ru=STATUS_DECLARED_UNIMPLEMENTED_LABEL_RU, + capabilities=["guardian", "security", "sandboxing"], + fallback_capabilities=["guardian", "security"], + default_preferred_chain=[], + max_failover_attempts=0, + tier="governance", + ), + "cost-controller": RoleDefinition( + role_id="cost-controller", + display_name_ru="Агент контроля затрат", + short_name_ru="Контроль затрат", + description_ru="Оценивает планируемый расход токенов, сравнивает с остатком бюджета, предлагает упрощения, сверяет факт с прогнозом, останавливает цепочку при исчерпании лимита.", + is_implemented=False, + unimplemented_reason_ru=STATUS_DECLARED_UNIMPLEMENTED_LABEL_RU, + capabilities=["cost-controller", "budget", "analytics"], + fallback_capabilities=["cost-controller", "budget"], + default_preferred_chain=[], + max_failover_attempts=0, + tier="governance", + ), + "integration-expert": RoleDefinition( + role_id="integration-expert", + display_name_ru="Специалист по интеграции", + short_name_ru="Интегратор", + description_ru="Работает с API и внешними сервисами, отправляет вебхуки.", + is_implemented=True, + capabilities=["integration-expert", "integration", "coding", "networking"], + fallback_capabilities=["integration", "coding"], + default_preferred_chain=["opengo-3", "codex-worker-1", "ag-w4"], + max_failover_attempts=3, + tier="expert", + ), + "security-expert": RoleDefinition( + role_id="security-expert", + display_name_ru="Юрист / специалист по безопасности", + short_name_ru="Безопасник", + description_ru="Проверяет код и данные на уязвимости.", + is_implemented=True, + capabilities=["security-expert", "security", "code-reviewer", "audit"], + fallback_capabilities=["security", "code-reviewer"], + default_preferred_chain=["claude-worker-1", "codex-worker-2", "ag-w2"], + max_failover_attempts=3, + tier="expert", + ), +} + +_CANONICAL_ROLE_ALIASES: Dict[str, str] = { + "orchestrator": "manager", + "coder-primary": "developer-1", + "coder-secondary": "developer-2", + "reviewer": "code-reviewer", + "research": "researcher", + "fast": "tester", +} + +class RoleRegistry: + @classmethod + def get_all_roles(cls) -> Dict[str, RoleDefinition]: + return dict(CANONICAL_ROLES) + + @classmethod + def get_role(cls, role_id: str) -> Optional[RoleDefinition]: + canonical_id = cls.resolve_canonical_role(role_id) + return CANONICAL_ROLES.get(canonical_id) + + @classmethod + def get_role_ids(cls) -> List[str]: + return list(CANONICAL_ROLES.keys()) + + @classmethod + def get_executable_role_ids(cls) -> List[str]: + return [r_id for r_id, r_def in CANONICAL_ROLES.items() if r_def.is_implemented] + + @classmethod + def get_unimplemented_role_ids(cls) -> List[str]: + return [r_id for r_id, r_def in CANONICAL_ROLES.items() if not r_def.is_implemented] + + @classmethod + def is_role_implemented(cls, role_id: str) -> bool: + r_def = cls.get_role(role_id) + return r_def.is_implemented if r_def else False + + @classmethod + def get_role_name_ru(cls, role_id: str, default: Optional[str] = None) -> str: + r_def = cls.get_role(role_id) + if r_def: + return r_def.display_name_ru + return default or role_id + + @classmethod + def get_role_short_name_ru(cls, role_id: str, default: Optional[str] = None) -> str: + r_def = cls.get_role(role_id) + if r_def: + return r_def.short_name_ru + return default or role_id + + @classmethod + def get_role_description_ru(cls, role_id: str) -> str: + r_def = cls.get_role(role_id) + if r_def: + return r_def.description_ru + return "" + + @classmethod + def resolve_canonical_role(cls, name_or_alias: str) -> str: + if not name_or_alias: + return "manager" + clean = name_or_alias.strip().lower() + return _CANONICAL_ROLE_ALIASES.get(clean, clean) + + @classmethod + def get_canonical_role_map(cls) -> Dict[str, str]: + return dict(_CANONICAL_ROLE_ALIASES) + + @classmethod + def get_human_role_labels(cls) -> Dict[str, str]: + labels = {} + for r_id, r_def in CANONICAL_ROLES.items(): + labels[r_id] = r_def.short_name_ru + labels.update({ + "spare_1": "Резерв 1", + "spare_2": "Резерв 2", + "cold_spare": "Холодный резерв", + }) + return labels + + @classmethod + def get_default_role_policies(cls) -> Dict[str, "RolePolicy"]: + from antigravity_provider.router.router_config import RolePolicy + policies: Dict[str, "RolePolicy"] = {} + for r_id, r_def in CANONICAL_ROLES.items(): + policies[r_id] = RolePolicy( + role_name=r_id, + preferred_chain=list(r_def.default_preferred_chain), + fallback_capabilities=list(r_def.fallback_capabilities), + max_failover_attempts=r_def.max_failover_attempts, + session_affinity_enabled=r_def.session_affinity_enabled, + default_model=r_def.default_model, + ) + return policies + + @classmethod + def migrate_legacy_roles(cls, current_roles: Dict[str, "RolePolicy"]) -> Tuple[Dict[str, "RolePolicy"], bool]: + from antigravity_provider.router.router_config import RolePolicy + migrated: Dict[str, "RolePolicy"] = {} + was_modified = False + default_policies = cls.get_default_role_policies() + + for rname, rpol in current_roles.items(): + canonical_id = cls.resolve_canonical_role(rname) + if canonical_id != rname: + was_modified = True + if canonical_id not in migrated: + migrated[canonical_id] = RolePolicy( + role_name=canonical_id, + preferred_chain=list(rpol.preferred_chain), + fallback_capabilities=list(rpol.fallback_capabilities) or list(default_policies[canonical_id].fallback_capabilities), + max_failover_attempts=rpol.max_failover_attempts, + session_affinity_enabled=rpol.session_affinity_enabled, + default_model=rpol.default_model or default_policies[canonical_id].default_model, + ) + else: + migrated[rname] = rpol + + for canon_id, def_policy in default_policies.items(): + if canon_id not in migrated: + migrated[canon_id] = def_policy + was_modified = True + + return migrated, was_modified diff --git a/src/antigravity_provider/router/router_config.py b/src/antigravity_provider/router/router_config.py index 386367d..91bbdaf 100644 --- a/src/antigravity_provider/router/router_config.py +++ b/src/antigravity_provider/router/router_config.py @@ -3,6 +3,7 @@ from __future__ import annotations import os from dataclasses import dataclass, field +from antigravity_provider.router.role_registry import RoleRegistry from pathlib import Path from typing import Any, Dict, List, Optional import yaml @@ -272,51 +273,7 @@ def get_default_router_config() -> RouterConfig: ), } - roles: dict[str, RolePolicy] = { - "orchestrator": RolePolicy( - role_name="orchestrator", - preferred_chain=["codex-orch", "ag-orch-fallback", "opengo-3"], - fallback_capabilities=["orchestrator", "reasoning"], - max_failover_attempts=3, - session_affinity_enabled=True, - default_model="gemini-3.7-flash", - ), - "coder-primary": RolePolicy( - role_name="coder-primary", - preferred_chain=["codex-worker-1", "ag-w1", "opengo-3"], - fallback_capabilities=["coding"], - max_failover_attempts=3, - session_affinity_enabled=True, - ), - "coder-secondary": RolePolicy( - role_name="coder-secondary", - preferred_chain=["codex-worker-2", "ag-w2", "opengo-2"], - fallback_capabilities=["coding", "reviewer"], - max_failover_attempts=3, - session_affinity_enabled=True, - ), - "reviewer": RolePolicy( - role_name="reviewer", - preferred_chain=["codex-worker-2", "opengo-2", "ag-w2"], - fallback_capabilities=["reviewer", "coding"], - max_failover_attempts=3, - session_affinity_enabled=True, - ), - "research": RolePolicy( - role_name="research", - preferred_chain=["opengo-1", "ag-w3", "ag-w4"], - fallback_capabilities=["research", "search"], - max_failover_attempts=3, - session_affinity_enabled=True, - ), - "fast": RolePolicy( - role_name="fast", - preferred_chain=["opengo-1", "ag-w4", "ag-spare-1"], - fallback_capabilities=["fast"], - max_failover_attempts=3, - session_affinity_enabled=True, - ), - } + roles = RoleRegistry.get_default_role_policies() return RouterConfig( enabled=True, diff --git a/src/antigravity_provider/router/telemetry_service.py b/src/antigravity_provider/router/telemetry_service.py index a797c77..838c720 100644 --- a/src/antigravity_provider/router/telemetry_service.py +++ b/src/antigravity_provider/router/telemetry_service.py @@ -8,6 +8,7 @@ Captures, persists, and computes honest empirical measurements for all router ca - Ring Buffer & File Rotation (bounded memory and disk footprint) """ from __future__ import annotations +from antigravity_provider.router.role_registry import RoleRegistry import collections import datetime @@ -399,7 +400,7 @@ class TelemetryService: global_aggs = self.get_aggregates(window_seconds=window_seconds) providers = set(known_providers or ["antigravity", "openai-codex", "opencode-go"]) - roles = set(known_roles or ["orchestrator", "coder-primary", "coder-secondary", "reviewer", "research", "fast"]) + roles = set(known_roles or RoleRegistry.get_role_ids()) with self._lock: for r in self._buffer: diff --git a/src/antigravity_provider/router/unified_health.py b/src/antigravity_provider/router/unified_health.py index 2cc157b..f6ee1dc 100644 --- a/src/antigravity_provider/router/unified_health.py +++ b/src/antigravity_provider/router/unified_health.py @@ -9,6 +9,7 @@ Single Source of Truth for: - EventLogService """ from __future__ import annotations +from antigravity_provider.router.role_registry import RoleRegistry import json import logging @@ -644,21 +645,38 @@ class UnifiedHealthService: config = load_router_config() self.scan_all(force=False) - ROLE_META = { - "orchestrator": ("Главный оркестратор", "Управление командой, планирование, контроль исполнения"), - "coder-primary": ("Кодер 1", "Основная разработка кода и исправление дефектов"), - "coder-secondary": ("Кодер 2", "Параллельная разработка и вспомогательные модули"), - "reviewer": ("Ревьюер", "Независимое fail-closed ревью и валидация diff"), - "research": ("Исследователь", "Read-only поиск в кодовой базе и сбор фактов"), - "fast": ("Быстрый агент", "Оперативные вызовы, вспомогательные проверки"), - "universal": ("Универсальный агент", "Широкий спектр общих задач"), - } - agents: List[AgentViewModel] = [] for rname, rpol in config.roles.items(): - rname_ru, rdesc_ru = ROLE_META.get(rname, (rname, "")) + rname_ru = RoleRegistry.get_role_name_ru(rname, rname) + rdesc_ru = RoleRegistry.get_role_description_ru(rname) + is_implemented = RoleRegistry.is_role_implemented(rname) + chain = rpol.preferred_chain + + if not is_implemented: + agents.append(AgentViewModel( + role_id=rname, + role_name_ru=rname_ru, + role_description_ru=rdesc_ru or "Роль объявлена, исполнение не реализовано", + assigned_profile_id=None, + assigned_display_name=None, + provider="N/A", + provider_display_name="N/A", + model="-", + account_identity="Роль объявлена, исполнение не реализовано", + routing_position="Отключено", + status="unimplemented", + status_label_ru="Не реализовано", + is_active=False, + is_main_orchestrator=False, + cooldown_remaining_sec=0, + session_id=None, + active_quota_status="unavailable", + active_quota_label="Не применяется (роль не активна)", + )) + continue + if not chain: continue @@ -703,7 +721,7 @@ class UnifiedHealthService: status=active_pvm.health_state, status_label_ru=active_pvm.health_label_ru, is_active=(active_pvm.health_state == STATUS_HEALTHY), - is_main_orchestrator=(rname == "orchestrator"), + is_main_orchestrator=(rname == "manager"), cooldown_remaining_sec=active_pvm.cooldown_remaining_sec, session_id=None, active_quota_status=active_quota_st, @@ -772,15 +790,7 @@ class UnifiedHealthService: self.scan_all(force=False) pipelines: Dict[str, RolePipeline] = {} - ROLE_NAMES = { - "orchestrator": "Главный оркестратор", - "coder-primary": "Кодер 1 (Primary)", - "coder-secondary": "Кодер 2 (Secondary)", - "reviewer": "Ревьюер", - "research": "Исследователь", - "fast": "Быстрый агент", - "universal": "Универсальный агент", - } + for rname, rpol in config.roles.items(): nodes: List[PipelineNode] = [] @@ -834,7 +844,7 @@ class UnifiedHealthService: pipelines[rname] = RolePipeline( role_id=rname, - role_name_ru=ROLE_NAMES.get(rname, rname), + role_name_ru=RoleRegistry.get_role_name_ru(rname, rname), default_model=rpol.default_model or "auto", max_failover=rpol.max_failover_attempts, session_affinity=rpol.session_affinity_enabled, diff --git a/tests/test_a9_migration_quotas_models.py b/tests/test_a9_migration_quotas_models.py index 178bc11..d8e4a34 100644 --- a/tests/test_a9_migration_quotas_models.py +++ b/tests/test_a9_migration_quotas_models.py @@ -62,7 +62,7 @@ class TestA9ConfigMigration(unittest.TestCase): legacy_cfg = RouterConfig( enabled=True, - default_role="orchestrator", + default_role="manager", roles=get_default_router_config().roles, profiles=legacy_profiles, ) @@ -316,9 +316,9 @@ class TestA9HermesPluginBoundary(unittest.TestCase): def test_resolve_role_respects_explicit_or_metadata_role(self): from antigravity_provider.router import get_router_engine engine = get_router_engine() - self.assertEqual(engine.resolve_role({}, explicit_role="coder-primary"), "coder-primary") - self.assertEqual(engine.resolve_role({"role": "reviewer"}), "reviewer") - self.assertEqual(engine.resolve_role({"metadata": {"role": "research"}}), "research") + self.assertEqual(engine.resolve_role({}, explicit_role="developer-1"), "developer-1") + self.assertEqual(engine.resolve_role({"role": "code-reviewer"}), "code-reviewer") + self.assertEqual(engine.resolve_role({"metadata": {"role": "researcher"}}), "researcher") class TestA9CodexOAuthTokenRefreshAndSwitching(unittest.TestCase): diff --git a/tests/test_accounts_distribution_and_thresholds_a26.py b/tests/test_accounts_distribution_and_thresholds_a26.py index 98a30ff..16de469 100644 --- a/tests/test_accounts_distribution_and_thresholds_a26.py +++ b/tests/test_accounts_distribution_and_thresholds_a26.py @@ -89,7 +89,8 @@ def test_single_account_assigned_to_all_six_roles_p0_3(): """P0-3: A single account can be assigned as primary across all 6 canonical roles without error.""" ProfileAuthManager.save_profile_auth("antigravity", "ag-w1", {"tokens": {"access_token": "token-1"}}) - canonical_roles = ["orchestrator", "coder-primary", "coder-secondary", "reviewer", "research", "fast"] + from antigravity_provider.router.role_registry import RoleRegistry + canonical_roles = list(RoleRegistry.get_role_ids()) for rname in canonical_roles: ok, msg = AutoAssigner.assign_profile_to_role("ag-w1", rname, is_primary=True) @@ -101,7 +102,7 @@ def test_single_account_assigned_to_all_six_roles_p0_3(): assert cfg.roles[rname].preferred_chain[0] == "ag-w1" # Verify action executor handles assign_role cleanly - res = ActionExecutor.execute("assign_role", {"role_id": "orchestrator", "profile_id": "ag-w1", "is_primary": True}) + res = ActionExecutor.execute("assign_role", {"role_id": "manager", "profile_id": "ag-w1", "is_primary": True}) assert res.get("ok") is True @@ -115,9 +116,11 @@ def test_auto_assign_all_single_account_p0_4(): assert result["total_authenticated"] == 1 cfg = load_router_config() - canonical_roles = ["orchestrator", "coder-primary", "coder-secondary", "reviewer", "research", "fast"] + from antigravity_provider.router.role_registry import RoleRegistry + canonical_roles = list(RoleRegistry.get_role_ids()) for rname in canonical_roles: - assert cfg.roles[rname].preferred_chain == ["ag-w1"] + if RoleRegistry.is_role_implemented(rname): + assert cfg.roles[rname].preferred_chain == ["ag-w1"] @pytest.mark.unit @@ -132,11 +135,11 @@ def test_auto_assign_all_two_accounts_same_provider_p0_4(): cfg = load_router_config() # Primary accounts should alternate between ag-w1 and ag-w2 - primaries = [cfg.roles[r].preferred_chain[0] for r in ["orchestrator", "coder-primary", "coder-secondary", "reviewer", "research", "fast"]] + primaries = [cfg.roles[r].preferred_chain[0] for r in ["manager", "developer-1", "developer-2", "code-reviewer", "researcher", "tester"]] assert "ag-w1" in primaries assert "ag-w2" in primaries # Fallback chains should contain the alternate account - for r in ["orchestrator", "coder-primary", "coder-secondary", "reviewer", "research", "fast"]: + for r in ["manager", "developer-1", "developer-2", "code-reviewer", "researcher", "tester"]: chain = cfg.roles[r].preferred_chain assert len(chain) == 2 assert set(chain) == {"ag-w1", "ag-w2"} @@ -155,10 +158,10 @@ def test_auto_assign_all_multi_provider_preferences_p0_4(): cfg = load_router_config() # Orchestrator prefers codex - assert cfg.roles["orchestrator"].preferred_chain[0] == "codex-orch" + assert cfg.roles["manager"].preferred_chain[0] == "codex-orch" # Research / fast prefers opencode - assert cfg.roles["research"].preferred_chain[0] == "opengo-1" - assert cfg.roles["fast"].preferred_chain[0] == "opengo-1" + assert cfg.roles["researcher"].preferred_chain[0] == "opengo-1" + assert cfg.roles["tester"].preferred_chain[0] == "opengo-1" @pytest.mark.unit diff --git a/tests/test_credential_isolation.py b/tests/test_credential_isolation.py index 3900278..df1dd40 100644 --- a/tests/test_credential_isolation.py +++ b/tests/test_credential_isolation.py @@ -164,8 +164,8 @@ def test_provider_selection_explanation_and_candidate_matrix(tmp_path, monkeypat "ag-healthy": RouterProfileConfig(profile_id="ag-healthy", provider="antigravity", enabled=True, preferred_models=["gemini-2.5-pro"]), }, roles={ - "orchestrator": RolePolicy( - role_name="orchestrator", + "manager": RolePolicy( + role_name="manager", preferred_chain=["ag-disabled", "ag-unauth", "ag-healthy"], max_failover_attempts=3, ) @@ -188,7 +188,7 @@ def test_provider_selection_explanation_and_candidate_matrix(tmp_path, monkeypat with patch.object(AntigravityAdapter, "invoke", side_effect=mock_invoke): - res = engine.route_request({"messages": [{"role": "user", "content": "explain"}]}, role="orchestrator") + res = engine.route_request({"messages": [{"role": "user", "content": "explain"}]}, role="manager") assert "router_metadata" in res meta = res["router_metadata"] @@ -227,8 +227,8 @@ def test_failover_exhaustion_includes_selection_trace(tmp_path, monkeypatch): "ag-w1": RouterProfileConfig(profile_id="ag-w1", provider="antigravity", enabled=True, preferred_models=["gemini-2.5-pro"]), }, roles={ - "orchestrator": RolePolicy( - role_name="orchestrator", + "manager": RolePolicy( + role_name="manager", preferred_chain=["ag-w1"], max_failover_attempts=1, ) @@ -246,7 +246,7 @@ def test_failover_exhaustion_includes_selection_trace(tmp_path, monkeypatch): with patch.object(ProfileAuthManager, "get_profile_status", return_value={"authenticated": True, "auth_state": "AUTHENTICATED"}), \ patch.object(AntigravityAdapter, "invoke", side_effect=mock_invoke_fail): - res = engine.route_request({"messages": [{"role": "user", "content": "hello"}]}, role="orchestrator") + res = engine.route_request({"messages": [{"role": "user", "content": "hello"}]}, role="manager") assert res.get("router_error") is True assert "selection_trace" in res diff --git a/tests/test_dashboard_data.py b/tests/test_dashboard_data.py index f316926..1a64b35 100644 --- a/tests/test_dashboard_data.py +++ b/tests/test_dashboard_data.py @@ -69,7 +69,7 @@ def test_provider_call_share_calculation(clean_services): # 2. Record 45 antigravity calls, 35 codex calls, 20 opencode calls (Total 100) for _ in range(45): ts.record_call( - role="orchestrator", + role="manager", profile_id="ag-orch", provider="antigravity", model="gemini-2.5-pro", @@ -81,7 +81,7 @@ def test_provider_call_share_calculation(clean_services): for _ in range(35): ts.record_call( - role="coder-primary", + role="developer-1", profile_id="codex-w1", provider="openai-codex", model="gpt-4o", @@ -93,7 +93,7 @@ def test_provider_call_share_calculation(clean_services): for _ in range(20): ts.record_call( - role="fast", + role="tester", profile_id="opengo-1", provider="opencode-go", model="deepseek-v4-flash", @@ -122,9 +122,9 @@ def test_provider_call_share_calculation(clean_services): assert breakdown["by_provider"]["antigravity"]["call_share"] == 0.45 assert breakdown["by_provider"]["openai-codex"]["call_share"] == 0.35 assert breakdown["by_provider"]["opencode-go"]["call_share"] == 0.20 - assert breakdown["by_role"]["orchestrator"]["total_calls"] == 45 - assert breakdown["by_role"]["coder-primary"]["total_calls"] == 35 - assert breakdown["by_role"]["fast"]["total_calls"] == 20 + assert breakdown["by_role"]["manager"]["total_calls"] == 45 + assert breakdown["by_role"]["developer-1"]["total_calls"] == 35 + assert breakdown["by_role"]["tester"]["total_calls"] == 20 @pytest.mark.unit @@ -211,8 +211,8 @@ def test_active_calls_and_hub_snapshot_integration(clean_services, tmp_path, mon ), }, roles={ - "orchestrator": RolePolicy( - role_name="orchestrator", + "manager": RolePolicy( + role_name="manager", preferred_chain=["ag-w1"], ) } @@ -227,7 +227,7 @@ def test_active_calls_and_hub_snapshot_integration(clean_services, tmp_path, mon # Record 1 call ts.record_call( - role="orchestrator", + role="manager", profile_id="ag-w1", provider="antigravity", model="gemini-2.5-pro", diff --git a/tests/test_health_auth_recovery.py b/tests/test_health_auth_recovery.py index 18c1e87..8f2a1f2 100644 --- a/tests/test_health_auth_recovery.py +++ b/tests/test_health_auth_recovery.py @@ -34,7 +34,7 @@ def isolated_env(tmp_path, monkeypatch): def test_auth_required_skipped_in_routing(isolated_env): tmp_path, ht = isolated_env - # Configure 2 profiles for role 'orchestrator' + # Configure 2 profiles for role 'manager' cfg = RouterConfig( profiles={ "ag-orch-1": RouterProfileConfig( @@ -49,12 +49,12 @@ def test_auth_required_skipped_in_routing(isolated_env): ), }, roles={ - "orchestrator": RolePolicy( - role_name="orchestrator", + "manager": RolePolicy( + role_name="manager", preferred_chain=["ag-orch-1", "ag-orch-2"], ), }, - default_role="orchestrator", + default_role="manager", ) engine = RouterEngine(config=cfg, health=ht) @@ -64,7 +64,7 @@ def test_auth_required_skipped_in_routing(isolated_env): # Initial state: ag-orch-1 is healthy and selected with patch.object(AntigravityAdapter, "invoke", side_effect=_mock_invoke): - res = engine.route_request({"prompt": "hi"}, role="orchestrator") + res = engine.route_request({"prompt": "hi"}, role="manager") assert res.get("router_metadata", {}).get("profile_id") == "ag-orch-1" # Mark ag-orch-1 as AUTH_REQUIRED @@ -73,7 +73,7 @@ def test_auth_required_skipped_in_routing(isolated_env): # Routing should skip ag-orch-1 and route to ag-orch-2 with patch.object(AntigravityAdapter, "invoke", side_effect=_mock_invoke): - res = engine.route_request({"prompt": "hi"}, role="orchestrator") + res = engine.route_request({"prompt": "hi"}, role="manager") assert res.get("router_metadata", {}).get("profile_id") == "ag-orch-2" @@ -94,12 +94,12 @@ def test_save_profile_auth_auto_recovery(isolated_env): ), }, roles={ - "orchestrator": RolePolicy( - role_name="orchestrator", + "manager": RolePolicy( + role_name="manager", preferred_chain=["ag-orch-1", "ag-orch-2"], ), }, - default_role="orchestrator", + default_role="manager", ) engine = RouterEngine(config=cfg, health=ht) @@ -128,7 +128,7 @@ def test_save_profile_auth_auto_recovery(isolated_env): # Router now routes back to primary profile ag-orch-1 with patch.object(AntigravityAdapter, "invoke", side_effect=_mock_invoke): - res = engine.route_request({"prompt": "hi"}, role="orchestrator") + res = engine.route_request({"prompt": "hi"}, role="manager") assert res.get("router_metadata", {}).get("profile_id") == "ag-orch-1" diff --git a/tests/test_local_llm_provider_a25.py b/tests/test_local_llm_provider_a25.py index 78d2631..088de7e 100644 --- a/tests/test_local_llm_provider_a25.py +++ b/tests/test_local_llm_provider_a25.py @@ -254,14 +254,14 @@ class TestLocalLLMConfigAndAutoAssigner: p1 = cfg.profiles["local-1"] assert p1.provider == "local" - assert "reviewer" in p1.capabilities + assert "code-reviewer" in p1.capabilities assert "coding" in p1.capabilities assert "Qwen3.8-27B-Q4_K_M.gguf" in p1.preferred_models assert p1.max_concurrency == 1 p2 = cfg.profiles["local-2"] assert p2.provider == "local" - assert "fast" in p2.capabilities + assert "tester" in p2.capabilities assert "Qwen3-4B-Instruct-2507-Q4_K_M.gguf" in p2.preferred_models assert p2.max_concurrency == 1 @@ -277,7 +277,7 @@ class TestLocalLLMConfigAndAutoAssigner: } legacy_cfg = RouterConfig( enabled=True, - default_role="orchestrator", + default_role="manager", roles=get_default_router_config().roles, profiles=legacy_profiles, ) diff --git a/tests/test_model_choice_and_discovery.py b/tests/test_model_choice_and_discovery.py index 60a0aff..cd64601 100644 --- a/tests/test_model_choice_and_discovery.py +++ b/tests/test_model_choice_and_discovery.py @@ -54,7 +54,7 @@ def test_set_model_success_and_config_persistence(temp_models_cache): res = ActionExecutor.execute("set_model", { "profile_id": profile_id, "model": target_model, - "role_id": "coder-primary", + "role_id": "developer-1", }) assert res["ok"] is True @@ -63,7 +63,7 @@ def test_set_model_success_and_config_persistence(temp_models_cache): # Verify persistence on disk reloaded = load_router_config() assert reloaded.profiles[profile_id].preferred_models[0] == target_model - assert reloaded.roles["coder-primary"].default_model == target_model + assert reloaded.roles["developer-1"].default_model == target_model def test_set_model_rejects_nonexistent_model(temp_models_cache): diff --git a/tests/test_model_validation_a23.py b/tests/test_model_validation_a23.py index 03693cc..f915e54 100644 --- a/tests/test_model_validation_a23.py +++ b/tests/test_model_validation_a23.py @@ -45,12 +45,12 @@ def isolated_hub(tmp_path, monkeypatch): ), }, roles={ - "orchestrator": RolePolicy( - role_name="orchestrator", + "manager": RolePolicy( + role_name="manager", preferred_chain=["ag-orch-1"], ), }, - default_role="orchestrator", + default_role="manager", ) save_router_config(cfg) return tmp_path diff --git a/tests/test_multi_provider_router.py b/tests/test_multi_provider_router.py index 373c560..0395924 100644 --- a/tests/test_multi_provider_router.py +++ b/tests/test_multi_provider_router.py @@ -77,17 +77,17 @@ class TestRouterConfig: def test_role_policies_chains(self): config = get_default_router_config() - assert "orchestrator" in config.roles - orch = config.roles["orchestrator"] + assert "manager" in config.roles + orch = config.roles["manager"] assert orch.preferred_chain == ["codex-orch", "ag-orch-fallback", "opengo-3"] - coder = config.roles["coder-primary"] + coder = config.roles["developer-1"] assert coder.preferred_chain == ["codex-worker-1", "ag-w1", "opengo-3"] - reviewer = config.roles["reviewer"] + reviewer = config.roles["code-reviewer"] assert reviewer.preferred_chain == ["codex-worker-2", "opengo-2", "ag-w2"] - research = config.roles["research"] + research = config.roles["researcher"] assert research.preferred_chain == ["opengo-1", "ag-w3", "ag-w4"] @@ -141,14 +141,14 @@ class TestSessionAffinityAndLeases: affinity = SessionAffinityTracker() assert affinity.get_affinity("sess-1") is None - affinity.set_affinity("sess-1", "orchestrator", "codex-orch", "gpt-4o") + affinity.set_affinity("sess-1", "manager", "codex-orch", "gpt-4o") rec = affinity.get_affinity("sess-1") assert rec is not None assert rec.profile_id == "codex-orch" - assert rec.role == "orchestrator" + assert rec.role == "manager" # Update on failover - affinity.set_affinity("sess-1", "orchestrator", "ag-orch-fallback", "gemini-3.7-flash") + affinity.set_affinity("sess-1", "manager", "ag-orch-fallback", "gemini-3.7-flash") rec2 = affinity.get_affinity("sess-1") assert rec2.profile_id == "ag-orch-fallback" @@ -182,14 +182,14 @@ class TestRouterEngineFailover: # 1. Normal state: codex-orch succeeds with patch.object(CodexAdapter, "invoke", return_value=mock_codex_response): - resp = engine.route_request({"messages": [{"role": "user", "content": "hello"}]}, role="orchestrator", session_id="sess-orch-1") + resp = engine.route_request({"messages": [{"role": "user", "content": "hello"}]}, role="manager", session_id="sess-orch-1") assert resp["choices"][0]["message"]["content"] == "from-codex" assert resp["router_metadata"]["profile_id"] == "codex-orch" # 2. Simulate quota on codex-orch: should auto-failover to ag-orch-fallback engine.health.simulate_quota("codex-orch", duration=600) with patch.object(AntigravityAdapter, "invoke", return_value=mock_ag_response): - resp2 = engine.route_request({"messages": [{"role": "user", "content": "hello again"}]}, role="orchestrator", session_id="sess-orch-2") + resp2 = engine.route_request({"messages": [{"role": "user", "content": "hello again"}]}, role="manager", session_id="sess-orch-2") assert resp2["choices"][0]["message"]["content"] == "from-antigravity" assert resp2["router_metadata"]["profile_id"] == "ag-orch-fallback" assert resp2["router_metadata"]["failover_count"] == 0 # picked directly because codex-orch was marked unhealthy @@ -197,7 +197,7 @@ class TestRouterEngineFailover: # 3. Simulate quota on both codex-orch and ag-orch-fallback: should failover to opengo-3 engine.health.simulate_quota("ag-orch-fallback", duration=600) with patch.object(OpenCodeGoAdapter, "invoke", return_value=mock_opengo_response): - resp3 = engine.route_request({"messages": [{"role": "user", "content": "hello third"}]}, role="orchestrator", session_id="sess-orch-3") + resp3 = engine.route_request({"messages": [{"role": "user", "content": "hello third"}]}, role="manager", session_id="sess-orch-3") assert resp3["choices"][0]["message"]["content"] == "from-opencode" assert resp3["router_metadata"]["profile_id"] == "opengo-3" @@ -220,13 +220,13 @@ class TestRouterEngineFailover: # Turn 1: codex-orch fails with quota exhaustion -> failover to ag-orch-fallback with patch.object(CodexAdapter, "invoke", side_effect=RuntimeError("Quota limit reached")): with patch.object(AntigravityAdapter, "invoke", return_value=mock_ag): - resp = engine.route_request({"messages": [{"role": "user", "content": "turn 1"}]}, role="orchestrator", session_id="session-user-123") + resp = engine.route_request({"messages": [{"role": "user", "content": "turn 1"}]}, role="manager", session_id="session-user-123") assert resp["choices"][0]["message"]["content"] == "a1" assert resp["router_metadata"]["profile_id"] == "ag-orch-fallback" # Turn 2: same session continues directly on ag-orch-fallback with patch.object(AntigravityAdapter, "invoke", return_value=mock_ag): - resp2 = engine.route_request({"messages": [{"role": "user", "content": "turn 2"}]}, role="orchestrator", session_id="session-user-123") + resp2 = engine.route_request({"messages": [{"role": "user", "content": "turn 2"}]}, role="manager", session_id="session-user-123") assert resp2["choices"][0]["message"]["content"] == "a1" assert resp2["router_metadata"]["profile_id"] == "ag-orch-fallback" @@ -256,9 +256,9 @@ class TestRouterCLI: rc = print_routing_policy() assert rc == 0 out = capsys.readouterr().out - assert "orchestrator" in out - assert "coder-primary" in out - assert "reviewer" in out + assert "manager" in out + assert "developer-1" in out + assert "code-reviewer" in out def test_simulate_quota_cli(self, capsys): rc = simulate_quota_cli("codex-orch", duration=300) diff --git a/tests/test_p0_release_gate.py b/tests/test_p0_release_gate.py index 88a9e68..1a0aeb9 100644 --- a/tests/test_p0_release_gate.py +++ b/tests/test_p0_release_gate.py @@ -126,7 +126,7 @@ def test_p0_4_auto_assign_all(tmp_path, monkeypatch): # Verify only canonical roles exist in config cfg = load_router_config() for rname in cfg.roles: - assert rname in {"orchestrator", "coder-primary", "coder-secondary", "reviewer", "research", "fast"} + assert rname in {"manager", "developer-1", "developer-2", "code-reviewer", "researcher", "tester"} @pytest.mark.unit @@ -148,8 +148,8 @@ def test_p0_5_antigravity_failover_on_quota(tmp_path, monkeypatch): ), }, roles={ - "orchestrator": RolePolicy( - role_name="orchestrator", + "manager": RolePolicy( + role_name="manager", preferred_chain=["ag-orch-primary", "codex-orch-fallback"], max_failover_attempts=2, ) @@ -175,7 +175,7 @@ def test_p0_5_antigravity_failover_on_quota(tmp_path, monkeypatch): with patch.object(AntigravityAdapter, "invoke", side_effect=mock_agy_invoke), \ patch.object(CodexAdapter, "invoke", side_effect=mock_codex_invoke): - res = engine.route_request({"messages": [{"role": "user", "content": "Hello"}]}, role="orchestrator") + res = engine.route_request({"messages": [{"role": "user", "content": "Hello"}]}, role="manager") # Must receive fallback response, NOT error text as message content! assert "choices" in res @@ -236,18 +236,18 @@ def test_p0_7_assign_role_action(tmp_path, monkeypatch): config = get_default_router_config() save_router_config(config) - # 1. Assign "coder" -> must update "coder-primary" + # 1. Assign "coder" -> must update "developer-1" ok, msg = AutoAssigner.assign_profile_to_role("ag-w1", "coder", is_primary=True) assert ok is True reloaded = load_router_config() - assert reloaded.roles["coder-primary"].preferred_chain[0] == "ag-w1" + assert reloaded.roles["developer-1"].preferred_chain[0] == "ag-w1" assert "coder" not in reloaded.roles # Must NOT create a non-canonical role - # 2. Assign "researcher" -> must update "research" + # 2. Assign "researcher" -> must update "researcher" ok, msg = AutoAssigner.assign_profile_to_role("ag-w2", "researcher", is_primary=True) assert ok is True reloaded = load_router_config() - assert reloaded.roles["research"].preferred_chain[0] == "ag-w2" + assert reloaded.roles["researcher"].preferred_chain[0] == "ag-w2" # 3. Unknown role -> must return False and reject ok, msg = AutoAssigner.assign_profile_to_role("ag-w1", "completely_unknown_role_xyz") @@ -262,7 +262,7 @@ def test_n1_spare_assignment_mode(tmp_path, monkeypatch): config = get_default_router_config() # Put ag-w1 in coder-primary - config.roles["coder-primary"].preferred_chain = ["ag-w1", "ag-w2"] + config.roles["developer-1"].preferred_chain = ["ag-w1", "ag-w2"] save_router_config(config) # Assign ag-w1 to spare @@ -271,7 +271,7 @@ def test_n1_spare_assignment_mode(tmp_path, monkeypatch): assert "резерв" in msg.lower() or "spare" in msg.lower() reloaded = load_router_config() - assert "ag-w1" not in reloaded.roles["coder-primary"].preferred_chain + assert "ag-w1" not in reloaded.roles["developer-1"].preferred_chain assert "spare" not in reloaded.roles # Canonical role set unchanged assert reloaded.profiles["ag-w1"].enabled is True @@ -303,12 +303,12 @@ def test_p0_8_wizard_role_application(tmp_path, monkeypatch): save_router_config(config) # Apply role - ok, msg = AutoAssigner.assign_profile_to_role("codex-worker-1", "reviewer", is_primary=True) + ok, msg = AutoAssigner.assign_profile_to_role("codex-worker-1", "code-reviewer", is_primary=True) assert ok is True reloaded = load_router_config() - assert "codex-worker-1" in reloaded.roles["reviewer"].preferred_chain - assert reloaded.roles["reviewer"].preferred_chain[0] == "codex-worker-1" + assert "codex-worker-1" in reloaded.roles["code-reviewer"].preferred_chain + assert reloaded.roles["code-reviewer"].preferred_chain[0] == "codex-worker-1" @pytest.mark.unit @@ -392,8 +392,8 @@ def test_r4_settings_runtime_influence(tmp_path, monkeypatch): "codex-orch-fallback": RouterProfileConfig(profile_id="codex-orch-fallback", provider="openai-codex", enabled=True), }, roles={ - "orchestrator": RolePolicy( - role_name="orchestrator", + "manager": RolePolicy( + role_name="manager", preferred_chain=["ag-orch-primary", "codex-orch-fallback"], max_failover_attempts=2, ) @@ -413,7 +413,7 @@ def test_r4_settings_runtime_influence(tmp_path, monkeypatch): with patch.object(AntigravityAdapter, "invoke", side_effect=mock_agy_quota), \ patch.object(CodexAdapter, "invoke", mock_codex): - res = engine.route_request({"messages": [{"role": "user", "content": "Hello"}]}, role="orchestrator") + res = engine.route_request({"messages": [{"role": "user", "content": "Hello"}]}, role="manager") assert "error" in res or "choices" in res # Codex fallback must NOT have been called because auto_failover was False! assert mock_codex.call_count == 0 @@ -425,7 +425,7 @@ def test_r4_settings_runtime_influence(tmp_path, monkeypatch): with patch.object(AntigravityAdapter, "invoke", side_effect=mock_agy_quota), \ patch.object(CodexAdapter, "invoke", mock_codex): - res = engine.route_request({"messages": [{"role": "user", "content": "Hello"}]}, role="orchestrator") + res = engine.route_request({"messages": [{"role": "user", "content": "Hello"}]}, role="manager") assert res["choices"][0]["message"]["content"] == "Fallback OK" assert mock_codex.call_count == 1 diff --git a/tests/test_plan_a_stabilization_and_smart_routing.py b/tests/test_plan_a_stabilization_and_smart_routing.py index 59eb236..65e4177 100644 --- a/tests/test_plan_a_stabilization_and_smart_routing.py +++ b/tests/test_plan_a_stabilization_and_smart_routing.py @@ -91,7 +91,7 @@ def test_event_bus_pub_sub(): # UI thread dispatch with root.after mock mock_root = MagicMock() - bus.publish_to_ui(mock_root, EVENT_ROUTING_UPDATED, {"role": "coder-primary"}) + bus.publish_to_ui(mock_root, EVENT_ROUTING_UPDATED, {"role": "developer-1"}) assert mock_root.after.called @@ -124,16 +124,16 @@ def test_refresh_scheduler_dedup_and_delays(): def test_session_affinity_ttl_and_lru(): tracker = SessionAffinityTracker(ttl_seconds=2, max_entries=3) - tracker.set_affinity("s1", "coder-primary", "ag-w1", "gemini-2.5-pro") - tracker.set_affinity("s2", "coder-primary", "ag-w2", "gemini-2.5-pro") - tracker.set_affinity("s3", "reviewer", "codex-w1", "gpt-4o") + tracker.set_affinity("s1", "developer-1", "ag-w1", "gemini-2.5-pro") + tracker.set_affinity("s2", "developer-1", "ag-w2", "gemini-2.5-pro") + tracker.set_affinity("s3", "code-reviewer", "codex-w1", "gpt-4o") assert tracker.get_affinity("s1") is not None assert tracker.get_affinity("s2") is not None assert tracker.get_affinity("s3") is not None # Exceed capacity -> triggers LRU eviction - tracker.set_affinity("s4", "fast", "ag-w3", "gemini-2.5-flash") + tracker.set_affinity("s4", "tester", "ag-w3", "gemini-2.5-flash") assert len(tracker._sessions) <= 3 # Test TTL expiration @@ -154,7 +154,7 @@ def test_model_registry_capability_filtering(): assert m_gemini_flash is not None # Reviewer requires security_analysis and coding - req_reviewer = reg.get_role_requirements("reviewer") + req_reviewer = reg.get_role_requirements("code-reviewer") ok_pro, score_pro, _ = reg.evaluate_model_score(m_gemini_pro, req_reviewer) ok_flash, score_flash, reason_flash = reg.evaluate_model_score(m_gemini_flash, req_reviewer) @@ -163,7 +163,7 @@ def test_model_registry_capability_filtering(): assert ok_flash is False # Fast role prioritizes latency - req_fast = reg.get_role_requirements("fast") + req_fast = reg.get_role_requirements("tester") ok_f_flash, score_f_flash, _ = reg.evaluate_model_score(m_gemini_flash, req_fast) ok_f_pro, score_f_pro, _ = reg.evaluate_model_score(m_gemini_pro, req_fast) @@ -174,7 +174,7 @@ def test_model_registry_capability_filtering(): # ── TEST 6: RouterEngine Dynamic Selection Trace & Same-Account Fallback ── def test_router_engine_selection_trace_and_same_account_fallback(): cfg = RouterConfig( - default_role="coder-primary", + default_role="developer-1", profiles={ "ag-w1": RouterProfileConfig( profile_id="ag-w1", @@ -190,8 +190,8 @@ def test_router_engine_selection_trace_and_same_account_fallback(): ), }, roles={ - "coder-primary": RolePolicy( - role_name="coder-primary", + "developer-1": RolePolicy( + role_name="developer-1", preferred_chain=["ag-w1", "codex-w1"], ), }, @@ -205,10 +205,10 @@ def test_router_engine_selection_trace_and_same_account_fallback(): mock_adapter.invoke.return_value = {"content": "code generated", "model": "google-antigravity/gemini-2.5-pro"} mock_adapter_getter.return_value = mock_adapter - res = engine.route_request({"messages": [{"role": "user", "content": "hello"}]}, role="coder-primary") + res = engine.route_request({"messages": [{"role": "user", "content": "hello"}]}, role="developer-1") assert "router_metadata" in res meta = res["router_metadata"] - assert meta["role"] == "coder-primary" + assert meta["role"] == "developer-1" assert meta["profile_id"] == "ag-w1" assert "selection_trace" in meta assert meta["selection_trace"]["selected_model"] == "google-antigravity/gemini-2.5-pro" diff --git a/tests/test_router_telemetry.py b/tests/test_router_telemetry.py index fd66398..1751153 100644 --- a/tests/test_router_telemetry.py +++ b/tests/test_router_telemetry.py @@ -39,7 +39,7 @@ def test_telemetry_recording_latency_tokens_outcome(temp_telemetry_service): svc = temp_telemetry_service rec = svc.record_call( - role="orchestrator", + role="manager", profile_id="ag-orch", provider="antigravity", model="gemini-2.5-pro", @@ -51,7 +51,7 @@ def test_telemetry_recording_latency_tokens_outcome(temp_telemetry_service): failover_count=0, ) - assert rec.role == "orchestrator" + assert rec.role == "manager" assert rec.profile_id == "ag-orch" assert rec.provider == "antigravity" assert rec.model == "gemini-2.5-pro" @@ -82,7 +82,7 @@ def test_telemetry_aggregates_calculation(temp_telemetry_service): outcome = "success" if i <= 8 else "error" error_cat = None if outcome == "success" else "quota_exhausted" svc.record_call( - role="coder-primary", + role="developer-1", profile_id="codex-w1", provider="openai-codex", model="gpt-4o", @@ -141,7 +141,7 @@ def test_telemetry_no_tokens_when_usage_missing(temp_telemetry_service): svc = temp_telemetry_service rec = svc.record_call( - role="fast", + role="tester", profile_id="opengo-1", provider="opencode-go", model="deepseek-v4-flash", @@ -170,7 +170,7 @@ def test_telemetry_no_secrets_and_no_request_response_content(temp_telemetry_ser # Record a normal call rec = svc.record_call( - role="orchestrator", + role="manager", profile_id="ag-w1", provider="antigravity", model="gemini-2.5-pro", @@ -228,7 +228,7 @@ def test_telemetry_cost_calculation_with_and_without_pricing(temp_telemetry_serv # 1. Without pricing -> cost_usd is None rec1 = svc.record_call( - role="coder-primary", + role="developer-1", profile_id="ag-w1", provider="antigravity", model="gemini-2.5-pro", @@ -245,7 +245,7 @@ def test_telemetry_cost_calculation_with_and_without_pricing(temp_telemetry_serv }) rec2 = svc.record_call( - role="coder-primary", + role="developer-1", profile_id="ag-w1", provider="antigravity", model="gemini-2.5-pro", @@ -279,8 +279,8 @@ def test_router_engine_records_telemetry_end_to_end(tmp_path, monkeypatch): ), }, roles={ - "orchestrator": RolePolicy( - role_name="orchestrator", + "manager": RolePolicy( + role_name="manager", preferred_chain=["ag-w1"], ) } @@ -304,7 +304,7 @@ def test_router_engine_records_telemetry_end_to_end(tmp_path, monkeypatch): with patch.object(TelemetryService, "get", return_value=svc), \ patch.object(AntigravityAdapter, "invoke", side_effect=mock_invoke): - res = engine.route_request({"messages": [{"role": "user", "content": "hello"}]}, role="orchestrator") + res = engine.route_request({"messages": [{"role": "user", "content": "hello"}]}, role="manager") assert "router_metadata" in res aggs = svc.get_aggregates() diff --git a/tests/test_routing_control_center_a24.py b/tests/test_routing_control_center_a24.py index e08223c..5db60cc 100644 --- a/tests/test_routing_control_center_a24.py +++ b/tests/test_routing_control_center_a24.py @@ -32,7 +32,7 @@ def temp_router_config(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): # Initialize sample profiles and roles config = RouterConfig( enabled=True, - default_role="orchestrator", + default_role="manager", profiles={ "ag-w1": RouterProfileConfig( profile_id="ag-w1", @@ -60,13 +60,13 @@ def temp_router_config(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): ), }, roles={ - "coder-primary": RolePolicy( - role_name="coder-primary", + "developer-1": RolePolicy( + role_name="developer-1", preferred_chain=["ag-w1", "ag-w2", "op-1"], default_model="gemini-2.5-pro", ), - "coder-secondary": RolePolicy( - role_name="coder-secondary", + "developer-2": RolePolicy( + role_name="developer-2", preferred_chain=["ag-w2", "op-1"], default_model="gemini-2.5-flash", ), @@ -79,14 +79,14 @@ def temp_router_config(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): def test_persist_role_chain_reorder_and_persistence(temp_router_config: Path): """Verify persist_role_chain updates preferred_chain and primary_profile_id and survives reload.""" new_chain: List[str] = ["op-1", "ag-w2", "ag-w1"] - ok, msg = AutoAssigner.persist_role_chain("coder-primary", new_chain) + ok, msg = AutoAssigner.persist_role_chain("developer-1", new_chain) assert ok is True assert "успешно сохранена" in msg # Reload from disk reloaded = load_router_config() - role_cfg = reloaded.roles["coder-primary"] + role_cfg = reloaded.roles["developer-1"] assert role_cfg.preferred_chain == new_chain assert role_cfg.preferred_chain[0] == "op-1" @@ -98,18 +98,18 @@ def test_persist_role_chain_canonical_name_mapping(temp_router_config: Path): assert ok is True reloaded = load_router_config() - assert reloaded.roles["coder-primary"].preferred_chain == new_chain + assert reloaded.roles["developer-1"].preferred_chain == new_chain def test_persist_role_chain_validation_errors(temp_router_config: Path): """Verify persist_role_chain rejects invalid profiles and duplicates.""" # Unknown profile - ok, msg = AutoAssigner.persist_role_chain("coder-primary", ["ag-w1", "non-existent-profile"]) + ok, msg = AutoAssigner.persist_role_chain("developer-1", ["ag-w1", "non-existent-profile"]) assert ok is False assert "не найден" in msg # Duplicate profile in chain - ok, msg = AutoAssigner.persist_role_chain("coder-primary", ["ag-w1", "ag-w1"]) + ok, msg = AutoAssigner.persist_role_chain("developer-1", ["ag-w1", "ag-w1"]) assert ok is False assert "повторяться" in msg or "дублир" in msg @@ -124,20 +124,20 @@ def test_action_executor_save_chain(temp_router_config: Path): executor = ActionExecutor() # save_chain - res1 = executor.execute("save_chain", {"role_id": "coder-primary", "chain": ["ag-w2", "ag-w1"]}) + res1 = executor.execute("save_chain", {"role_id": "developer-1", "chain": ["ag-w2", "ag-w1"]}) assert res1["ok"] is True # reorder_chain - res2 = executor.execute("reorder_chain", {"role_id": "coder-primary", "desired_chain": ["op-1", "ag-w1"]}) + res2 = executor.execute("reorder_chain", {"role_id": "developer-1", "desired_chain": ["op-1", "ag-w1"]}) assert res2["ok"] is True # edit_route - res3 = executor.execute("edit_route", {"role_id": "coder-secondary", "chain": ["op-1", "ag-w2"]}) + res3 = executor.execute("edit_route", {"role_id": "developer-2", "chain": ["op-1", "ag-w2"]}) assert res3["ok"] is True reloaded = load_router_config() - assert reloaded.roles["coder-primary"].preferred_chain == ["op-1", "ag-w1"] - assert reloaded.roles["coder-secondary"].preferred_chain == ["op-1", "ag-w2"] + assert reloaded.roles["developer-1"].preferred_chain == ["op-1", "ag-w1"] + assert reloaded.roles["developer-2"].preferred_chain == ["op-1", "ag-w2"] def test_action_executor_set_model_updates_profile_and_role(temp_router_config: Path): @@ -147,13 +147,13 @@ def test_action_executor_set_model_updates_profile_and_role(temp_router_config: res = executor.execute("set_model", { "profile_id": "ag-w1", "model": "gemini-3.1-pro", - "role_id": "coder-primary", + "role_id": "developer-1", }) assert res["ok"] is True reloaded = load_router_config() assert reloaded.profiles["ag-w1"].preferred_models[0] == "gemini-3.1-pro" - assert reloaded.roles["coder-primary"].default_model == "gemini-3.1-pro" + assert reloaded.roles["developer-1"].default_model == "gemini-3.1-pro" def test_web_client_7_views_exact_order_and_no_redundant_views(): diff --git a/tests/test_state_layer_and_event_driven_quota.py b/tests/test_state_layer_and_event_driven_quota.py index 5b088fa..bcc8724 100644 --- a/tests/test_state_layer_and_event_driven_quota.py +++ b/tests/test_state_layer_and_event_driven_quota.py @@ -177,16 +177,16 @@ def test_route_and_agent_delta_events(): bus.subscribe("AGENT_UPDATED", _on_agent) try: - store.apply_delta_route_changed("coder-primary", "ag-w1", failover_reason="Testing failover") + store.apply_delta_route_changed("developer-1", "ag-w1", failover_reason="Testing failover") assert len(route_events) >= 1 - assert route_events[-1]["role_id"] == "coder-primary" + assert route_events[-1]["role_id"] == "developer-1" assert route_events[-1]["failover_reason"] == "Testing failover" assert "generation" in route_events[-1] assert "seq" in route_events[-1] assert len(agent_events) >= 1 - assert agent_events[-1]["role_id"] == "coder-primary" - assert agent_events[-1]["agent"].role_id == "coder-primary" + assert agent_events[-1]["role_id"] == "developer-1" + assert agent_events[-1]["agent"].role_id == "developer-1" finally: bus.unsubscribe(EVENT_ROUTING_UPDATED, _on_route) bus.unsubscribe("AGENT_UPDATED", _on_agent) diff --git a/tests/test_state_layer_contract.py b/tests/test_state_layer_contract.py index 711dc09..d3cc519 100644 --- a/tests/test_state_layer_contract.py +++ b/tests/test_state_layer_contract.py @@ -159,7 +159,7 @@ def test_single_scheduler_refresh_waits_for_quota_before_delta(monkeypatch: pyte def test_model_score_rejects_exhausted_pool_and_prefers_more_quota() -> None: registry = ModelRegistry.get() descriptor = registry.get_model("gemini-2.5-pro") - requirements = registry.get_role_requirements("research") + requirements = registry.get_role_requirements("researcher") assert descriptor is not None ok_empty, _, reason = registry.evaluate_model_score( diff --git a/tests/test_ui_claude_grok_connection.py b/tests/test_ui_claude_grok_connection.py index c9e5514..340fc1f 100644 --- a/tests/test_ui_claude_grok_connection.py +++ b/tests/test_ui_claude_grok_connection.py @@ -34,7 +34,7 @@ def test_grok_wizard_definition_and_routing_flow(): """Verify Grok profile definition and routing pipeline addition.""" config = RouterConfig( profiles={}, - roles={"coder-primary": RolePolicy(role_name="coder-primary", preferred_chain=[])}, + roles={"developer-1": RolePolicy(role_name="developer-1", preferred_chain=[])}, ) with patch("antigravity_provider.router.auto_assigner.load_router_config", return_value=config), \ patch("antigravity_provider.router.auto_assigner.save_router_config", return_value=True), \ @@ -47,7 +47,7 @@ def test_grok_wizard_definition_and_routing_flow(): ok_route, msg_route = ensure_profile_in_routing("grok-worker-1") assert ok_route, f"Routing failed: {msg_route}" - assert "grok-worker-1" in config.roles["coder-primary"].preferred_chain + assert "grok-worker-1" in config.roles["developer-1"].preferred_chain @pytest.mark.unit @@ -55,7 +55,7 @@ def test_claude_wizard_definition_and_routing_flow(): """Verify Claude profile definition and routing pipeline addition.""" config = RouterConfig( profiles={}, - roles={"orchestrator": RolePolicy(role_name="orchestrator", preferred_chain=[])}, + roles={"manager": RolePolicy(role_name="manager", preferred_chain=[])}, ) with patch("antigravity_provider.router.auto_assigner.load_router_config", return_value=config), \ patch("antigravity_provider.router.auto_assigner.save_router_config", return_value=True), \ @@ -68,7 +68,7 @@ def test_claude_wizard_definition_and_routing_flow(): ok_route, msg_route = ensure_profile_in_routing("claude-orch") assert ok_route, f"Routing failed: {msg_route}" - assert "claude-orch" in config.roles["orchestrator"].preferred_chain + assert "claude-orch" in config.roles["manager"].preferred_chain @pytest.mark.unit diff --git a/tests/test_ui_contract_v11.py b/tests/test_ui_contract_v11.py index 67e31cf..044fd33 100644 --- a/tests/test_ui_contract_v11.py +++ b/tests/test_ui_contract_v11.py @@ -43,8 +43,8 @@ def _profile(plan_code: str = "PRO", plan_source: str = "provider_api") -> Profi account_identity="user@example.test", provider="antigravity", provider_display_name="Google Antigravity", - assigned_roles=["coder-primary"], - primary_role="coder-primary", + assigned_roles=["developer-1"], + primary_role="developer-1", is_main_account=True, is_main_orchestrator=False, auth_state="AUTHENTICATED", @@ -143,7 +143,7 @@ def test_quota_missing_is_not_rendered_as_zero_and_reason_is_visible(ui_root) -> @pytest.mark.ui def test_agent_quota_and_failover_reason_are_bound_to_their_models(ui_root) -> None: agent = AgentViewModel( - role_id="coder-primary", + role_id="developer-1", role_name_ru="Кодер 1", role_description_ru="Основной кодер", assigned_profile_id="account-2", @@ -163,7 +163,7 @@ def test_agent_quota_and_failover_reason_are_bound_to_their_models(ui_root) -> N ) team_card = AgentCardWidget(ui_root) pipeline = RolePipeline( - role_id="coder-primary", + role_id="developer-1", role_name_ru="Кодер 1", default_model="gpt-5", max_failover=2, @@ -214,7 +214,7 @@ def test_agent_quota_and_failover_reason_are_bound_to_their_models(ui_root) -> N def test_dashboard_agent_quota_measurement_drives_progress_percent() -> None: agent = AgentViewModel( - role_id="coder-primary", + role_id="developer-1", role_name_ru="Кодер 1", role_description_ru="Основной кодер", assigned_profile_id="ag-w1", diff --git a/tests/test_ui_mockup_redesign.py b/tests/test_ui_mockup_redesign.py index 358f155..3c87789 100644 --- a/tests/test_ui_mockup_redesign.py +++ b/tests/test_ui_mockup_redesign.py @@ -130,7 +130,7 @@ def test_restored_account_buttons_invoke_each_action(ui_root) -> None: @pytest.mark.ui def test_assign_role_error_stays_visible_in_open_modal(ui_root, monkeypatch) -> None: policy = SimpleNamespace(preferred_chain=[]) - monkeypatch.setattr(app_module, "load_router_config", lambda: SimpleNamespace(roles={"orchestrator": policy})) + monkeypatch.setattr(app_module, "load_router_config", lambda: SimpleNamespace(roles={"manager": policy})) monkeypatch.setattr( app_module.AutoAssigner, "assign_profile_to_role", diff --git a/tests/test_ui_phase2_6.py b/tests/test_ui_phase2_6.py index 2518990..9a8cf43 100644 --- a/tests/test_ui_phase2_6.py +++ b/tests/test_ui_phase2_6.py @@ -37,8 +37,8 @@ def _profile(index: int) -> ProfileViewModel: account_identity=f"user-{index}@example.test", provider="antigravity", provider_display_name="Google Antigravity", - assigned_roles=["coder" if index else "orchestrator"], - primary_role="coder" if index else "orchestrator", + assigned_roles=["coder" if index else "manager"], + primary_role="coder" if index else "manager", is_main_account=index == 0, is_main_orchestrator=index == 0, auth_state="AUTHENTICATED", diff --git a/tests/test_ui_routing_graph.py b/tests/test_ui_routing_graph.py index 08cb872..eedd20d 100644 --- a/tests/test_ui_routing_graph.py +++ b/tests/test_ui_routing_graph.py @@ -29,12 +29,12 @@ def _config(): "coder": SimpleNamespace(provider="antigravity", preferred_models=["gemini"]), } roles = { - "orchestrator": SimpleNamespace(preferred_chain=["orch"]), - "coder-primary": SimpleNamespace(preferred_chain=["coder"]), - "coder-secondary": SimpleNamespace(preferred_chain=["coder"]), - "reviewer": SimpleNamespace(preferred_chain=["coder"]), - "research": SimpleNamespace(preferred_chain=["coder"]), - "fast": SimpleNamespace(preferred_chain=["coder"]), + "manager": SimpleNamespace(preferred_chain=["orch"]), + "developer-1": SimpleNamespace(preferred_chain=["coder"]), + "developer-2": SimpleNamespace(preferred_chain=["coder"]), + "code-reviewer": SimpleNamespace(preferred_chain=["coder"]), + "researcher": SimpleNamespace(preferred_chain=["coder"]), + "tester": SimpleNamespace(preferred_chain=["coder"]), } return SimpleNamespace(roles=roles, profiles=profiles) @@ -83,16 +83,16 @@ def test_graph_layout_zoom_and_viewport_survive_restart(tmp_path): def test_validation_finds_cycle_unreachable_and_missing_profile(): config = _config() - config.roles["reviewer"].preferred_chain = ["ghost"] + config.roles["code-reviewer"].preferred_chain = ["ghost"] graph = RoutingGraph( nodes=[ - GraphNode("orchestrator", 0, 0), - GraphNode("coder-primary", 1, 0), - GraphNode("reviewer", 2, 0), + GraphNode("manager", 0, 0), + GraphNode("developer-1", 1, 0), + GraphNode("code-reviewer", 2, 0), ], edges=[ - GraphEdge("orchestrator", "coder-primary"), - GraphEdge("coder-primary", "orchestrator"), + GraphEdge("manager", "developer-1"), + GraphEdge("developer-1", "manager"), ], ) codes = {issue.code for issue in validate_graph(graph, config)} @@ -108,16 +108,16 @@ def test_profile_edge_updates_yaml_via_auto_assigner(monkeypatch, tmp_path): lambda profile, role, is_primary: calls.append((profile, role, is_primary)) or (True, "ok"), ) controller = RoutingGraphController(RoutingGraphStore(tmp_path / "graph.json")) - ok, _message = controller.add_edge("orchestrator", "coder-primary", "FALLBACK", "orch") + ok, _message = controller.add_edge("manager", "developer-1", "FALLBACK", "orch") assert ok - assert calls == [("orch", "coder-primary", False)] + assert calls == [("orch", "developer-1", False)] def test_undo_redo_and_dirty_state(monkeypatch, tmp_path): monkeypatch.setattr(graph_module, "load_router_config", _config) controller = RoutingGraphController(RoutingGraphStore(tmp_path / "graph.json")) original = controller.graph.nodes[0].x - controller.move_node("orchestrator", original + 100, 50) + controller.move_node("manager", original + 100, 50) assert controller.dirty assert controller.undo() assert controller.graph.nodes[0].x == original @@ -255,8 +255,8 @@ def test_role_chain_order_and_removal_persist_through_auto_assigner(monkeypatch) config = SimpleNamespace( profiles={key: SimpleNamespace() for key in ("a", "b", "c")}, roles={ - "orchestrator": SimpleNamespace(preferred_chain=["a", "b", "c"]), - "reviewer": SimpleNamespace(preferred_chain=["b"]), + "manager": SimpleNamespace(preferred_chain=["a", "b", "c"]), + "code-reviewer": SimpleNamespace(preferred_chain=["b"]), }, ) calls = [] @@ -279,11 +279,11 @@ def test_role_chain_order_and_removal_persist_through_auto_assigner(monkeypatch) monkeypatch.setattr(team_module, "load_router_config", lambda: config) monkeypatch.setattr(team_module.AutoAssigner, "assign_profile_to_role", assign) - ok, _message = team_module.persist_role_chain("orchestrator", ["c", "a"]) + ok, _message = team_module.persist_role_chain("manager", ["c", "a"]) assert ok - assert config.roles["orchestrator"].preferred_chain == ["c", "a"] - assert config.roles["reviewer"].preferred_chain == ["b"] + assert config.roles["manager"].preferred_chain == ["c", "a"] + assert config.roles["code-reviewer"].preferred_chain == ["b"] assert ("b", "spare", False) in calls diff --git a/tests/test_unified_health.py b/tests/test_unified_health.py index d3c9b9d..2d2f54c 100644 --- a/tests/test_unified_health.py +++ b/tests/test_unified_health.py @@ -96,7 +96,7 @@ def test_main_account_vs_orchestrator_separation(): service.scan_all() agents = service.get_agent_view_models() - orch_agent = next((a for a in agents if a.role_id == "orchestrator"), None) + orch_agent = next((a for a in agents if a.role_id == "manager"), None) assert orch_agent is not None assert orch_agent.is_main_orchestrator is True