diff --git a/agents/hybrid.py b/agents/hybrid.py index a4695d0..813280a 100644 --- a/agents/hybrid.py +++ b/agents/hybrid.py @@ -388,7 +388,16 @@ def _handle_plan_sketch(self, plan_text: Optional[str]) -> None: if "(" in token and token.endswith(")"): prefix, arg_str = token.split("(", 1) verb_name = prefix.strip() - args = [arg_str[:-1]] if arg_str.endswith(")") else [arg_str] + if arg_str.endswith(")"): + arg_str = arg_str[:-1] + raw_args = [part for part in arg_str.split(",") if part.strip()] + + def _clean_argument(value: str) -> str: + cleaned = value.strip().rstrip(")").strip() + cleaned = cleaned.strip("\"'") + return cleaned + + args = [_clean_argument(part) for part in raw_args] try: verb = DSLVerb(verb_name) except ValueError: diff --git a/flappy/synth.py b/flappy/synth.py index e9f8edd..3916e46 100644 --- a/flappy/synth.py +++ b/flappy/synth.py @@ -64,7 +64,7 @@ def _extract_selectors(self, context: Optional[dict]) -> Sequence[str]: def _plan_compatible(self, node: DSLNode, selectors: Sequence[str]) -> bool: if node.verb in {DSLVerb.CLICK, DSLVerb.TYPE} and node.args: - selector = node.args[0] + selector = self._normalise_selector(node.args[0]) if selector and selectors and selector not in selectors: return False for child in node.children: @@ -72,5 +72,12 @@ def _plan_compatible(self, node: DSLNode, selectors: Sequence[str]) -> bool: return False return True + @staticmethod + def _normalise_selector(raw_selector: str) -> str: + token = raw_selector.split(",", 1)[0].strip() + token = token.rstrip(")").strip() + token = token.strip("\"'") + return token + __all__ = ["Sketch", "CandidatePlan", "PlanSynthesiser"] diff --git a/flappy/verify.py b/flappy/verify.py index 6fe9e3e..7424360 100644 --- a/flappy/verify.py +++ b/flappy/verify.py @@ -59,12 +59,19 @@ def _extract_selectors(self, context: Optional[dict]) -> Sequence[str]: def _find_missing_selectors(self, node: DSLNode, selectors: Sequence[str]) -> set[str]: missing: set[str] = set() if node.verb in {DSLVerb.CLICK, DSLVerb.TYPE} and node.args: - selector = node.args[0] + selector = self._normalise_selector(node.args[0]) if selector and selectors and selector not in selectors: missing.add(selector) for child in node.children: missing.update(self._find_missing_selectors(child, selectors)) return missing + @staticmethod + def _normalise_selector(raw_selector: str) -> str: + token = raw_selector.split(",", 1)[0].strip() + token = token.rstrip(")").strip() + token = token.strip("\"'") + return token + __all__ = ["PlanVerifier", "VerificationResult", "Predicate"] diff --git a/tests/test_flappy_stubs.py b/tests/test_flappy_stubs.py index e606251..03fd27e 100644 --- a/tests/test_flappy_stubs.py +++ b/tests/test_flappy_stubs.py @@ -45,6 +45,19 @@ def test_plan_synthesiser_identity(): assert proposals[0].root.to_dict() == leaf.to_dict() +def test_selector_normalisation_for_comma_arguments(): + plan = dsl.DSLNode(verb=dsl.DSLVerb.TYPE, args=["#field, hello"]) + sketch = synth.Sketch(root=plan, holes=[]) + context = {"selectors": ["#field"]} + + proposals = list(synth.PlanSynthesiser().enumerate(sketch, context=context)) + assert proposals, "Synthesiser should return the plan when selector matches" + + verifier = verify.PlanVerifier() + result = verifier.verify(plan, trace=[], context=context) + assert result.ok, "Verifier should accept plans with normalised selectors" + + def test_verifier_stub(): verifier = verify.PlanVerifier() result = verifier.verify(