"""Exercise loop boundaries without booting the application or its services.""" import ast from pathlib import Path from types import SimpleNamespace import pytest SOURCE = Path(__file__).resolve().parents[1] / "src" / "agent_loop.py" def load_function(name, namespace=None): tree = ast.parse(SOURCE.read_text()) node = next(n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef) and n.name == name) namespace = {} if namespace is None else namespace exec(compile(ast.Module(body=[node], type_ignores=[]), str(SOURCE), "exec"), namespace) return namespace[name] def contract(*families): schema = {"type": "function", "function": {"name": "manage_calendar"}} return SimpleNamespace( capabilities=frozenset(families), required=frozenset(), offered=frozenset({"manage_calendar"}) if families else frozenset(), permits=lambda name: bool(families) and name == "manage_calendar", schemas=lambda: [schema] if families else [], ) @pytest.mark.parametrize("families", [("calendar",), ("calendar", "documents")]) def test_contract_work_cannot_finish_through_single_action_shortcut(families): allows = load_function("_contract_allows_early_completion") assert not allows(contract(*families)) assert allows(None) assert allows(contract()) @pytest.mark.parametrize("policy", [None, SimpleNamespace(blocks=lambda _: False)]) def test_contract_rejection_needs_no_legacy_denial(policy): reason = load_function("_tool_rejection_reason") assert "outside" in reason("bash", {"bash"}, policy, contract("calendar")) assert "disabled" in reason("bash", {"bash"}, policy) @pytest.mark.parametrize("surface,is_api,router,expected", [ ("none", True, False, []), ("", False, False, []), ("", True, True, []), ("compact", True, True, ["manage_calendar"]), ("full", True, False, ["manage_calendar"]), ]) def test_contract_schema_transport(surface, is_api, router, expected): fn = schema_function() schemas = fn(route(surface, is_api, router)) assert [s["function"]["name"] for s in schemas] == expected def route(surface="compact", is_api=True, router=False): return {"mcp_schemas": [], "relevant_tools": {"bash"}, "qwen38_tool_router": router, "tool_surface": surface, "is_api_model": is_api} def schema_function(force_answer=False, keep_artifacts=False, guide_only=False): namespace = { "turn_contract": contract("calendar"), "guide_only": guide_only, "_force_answer": force_answer, "_artifact_recovery_enabled": False, "_artifact_creation_requested": False, "_artifact_finish_nudge_sent": False, "_artifact_finish_correction_seen": False, "_artifact_finish_post_correction_tool_used": False, "_artifact_finish_post_correction_mutation_seen": False, "_post_correction_verification_available": lambda **_: False, "_force_answer_keeps_artifact_tools": lambda **_: keep_artifacts, "_normalize_model_tool_surface": lambda value: value, "_tui_local_workspace_turn": lambda *a, **kw: False, "_retrieval_query": "", "_last_user": "", "workspace": None, "client_runtime_context": None, "_apply_tool_surface_to_schemas": lambda schemas, surface: schemas, } return load_function("_tool_schemas_for_route", namespace) def test_synthesis_preserves_existing_artifact_exception(): assert schema_function(force_answer=True)(route()) == [] assert schema_function(force_answer=True, keep_artifacts=True)(route()) assert schema_function(guide_only=True)(route()) == [] def test_early_shortcuts_are_guarded_in_the_loop(): tree = ast.parse(SOURCE.read_text()) triggers = {"_early_active_email_reply_body", "_early_active_email_draft_update", "_early_active_document_append", "_no_tool_boundary_answer", "_exact_file_edit"} found = set() for node in ast.walk(tree): if not isinstance(node, ast.If): continue names = {n.id for n in ast.walk(node.test) if isinstance(n, ast.Name)} matched = names & triggers if matched: assert "_contract_allows_early_completion" in names found.update(matched) assert found == triggers @pytest.mark.parametrize("eligible,summary", [(False, "Thirty notes"), (True, "")]) def test_successful_tool_continues_to_synthesis_without_eligible_answer(eligible, summary): """Run the actual post-tool completion branch, including its break.""" tree = ast.parse(SOURCE.read_text()) node = next(n for n in ast.walk(tree) if isinstance(n, ast.If) and "_ody_notes_tool_completed" in { v.id for v in ast.walk(n.test) if isinstance(v, ast.Name)}) reached = [] namespace = dict( _ody_notes_finetune_mode=False, _ody_qwen_finetune_model=False, _qwen38_tool_router=True, _ody_notes_tool_completed=True, _deterministic_terminal_eligible=eligible, _qwen_note_delete_title="", _qwen_note_view_title="", _qwen_latest_email_open=False, _qwen_latest_email_open_completed=False, _qwen_latest_email_action=None, _qwen_latest_email_action_completed=False, tool_result_records=[{"tool_name": "list_cookbook_servers", "content": "", "result": {"output": "6 configured servers", "exit_code": 0}}], _ody_qwen_terminal_tool_summary=lambda *a, **kw: summary, _latest_email_action_needs_followup=lambda *a: False, _last_user="List servers", full_response="", logger=SimpleNamespace(info=lambda *a: None), mark_synthesis=lambda: reached.append(True), ) # Add the actual condition's locator flags with neutral defaults. for name in {n.id for n in ast.walk(node) if isinstance(n, ast.Name)}: if name.startswith("_qwen_"): namespace.setdefault(name, False) wrapper = ast.parse("def run():\n for _ in range(1):\n pass\n").body[0] wrapper.body[0].body = [node, ast.Expr(value=ast.Call(func=ast.Name(id="mark_synthesis", ctx=ast.Load()), args=[], keywords=[]))] module = ast.fix_missing_locations(ast.Module(body=[wrapper], type_ignores=[])) exec(compile(module, str(SOURCE), "exec"), namespace) list(namespace["run"]()) assert reached == [True]