diff --git a/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py b/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py index b8babc81..6b761598 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/scripts/dispatch.py @@ -101,11 +101,13 @@ VERDICT_SCHEMA_MATCHERS = tuple( ( re.compile(rf"^##\s*{re.escape(heading)}[ \t]*$", re.MULTILINE), re.compile( - rf"^(?:-\s*)?(?:\*\*)?{re.escape(label)}(?:\*\*)?\s*:\s*(PASS|WARN|FAIL)[ \t]*$", + rf"^(?:-\s*)?(?:\*\*)?{re.escape(label)}(?:\*\*)?\s*:\s*" + rf"(?:(PASS|WARN|FAIL)|`(PASS|WARN|FAIL)`)[ \t]*$", re.MULTILINE, ), re.compile( - rf"^###\s+{re.escape(label)}[ \t]*$\s*^(?:\*\*)?(PASS|WARN|FAIL)(?:\*\*)?[ \t]*$", + rf"^###\s+{re.escape(label)}[ \t]*$\s*^(?:\*\*)?" + rf"(?:(PASS|WARN|FAIL)|`(PASS|WARN|FAIL)`)(?:\*\*)?[ \t]*$", re.MULTILINE, ), ) @@ -203,6 +205,7 @@ RUNTIME_FAILURE_PATTERNS = { "model-unavailable": [ r"model.{0,40}(?:not found|unavailable)", r"overloaded", r"temporarily unavailable", + r"reasoning[_ ]effort must be one of", ], "provider-connection": [ r"\bprovider[_ -]?tunnel[_ -]?error\b", @@ -4349,7 +4352,12 @@ def verdict_from_text(text: str) -> str | None: inline_matches = list(line_re.finditer(section)) block_matches = list(block_re.finditer(section)) matches = inline_matches + block_matches - return matches[0].group(1) if len(matches) == 1 else None + if len(matches) != 1: + return None + return next( + (value for value in matches[0].groups() if value in {"PASS", "WARN", "FAIL"}), + None, + ) def matching_archive_directories_by_name( diff --git a/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatch.py b/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatch.py index fe5f141b..3116f091 100644 --- a/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatch.py +++ b/agent-ops/skills/common/orchestrate-agent-task-loop/tests/test_dispatch.py @@ -456,6 +456,22 @@ class RuntimeCatalogDispatcherTests(unittest.TestCase): class GenericDispatcherContractTests(unittest.TestCase): + def test_verdict_parser_accepts_inline_code_value(self): + text = "## Code Review Result\n\n- Overall Verdict: `FAIL`\n" + + self.assertEqual(dispatch.verdict_from_text(text), "FAIL") + + def test_invalid_reasoning_effort_is_target_unavailable(self): + output = ( + '{"type":"error","error":{"data":{"message":' + '"reasoning_effort must be one of none, low, medium, or high"}}}' + ) + + failure, evidence = dispatch.classify_failure_with_evidence(output) + + self.assertEqual(failure, "model-unavailable") + self.assertIn("reasoning_effort", evidence or "") + def test_selfcheck_work_log_uses_worker_plan_artifact(self): with TemporaryDirectory() as tmp: directory = Path(tmp) / "agent-task" / "group" / "01_task"