Give clickRef to prima only, the one agent whose context carries refs - #130
Merged
Conversation
PR #110 said what should happen and then did the opposite: A tool's schema is shared with every caller, and Tester never receives ref-bearing snapshots, so it must not be shown a ref parameter it could only fill by inventing one. The ref tools live in the boat that uses them. The squashed commit put clickRef in createCodeceptJSTools, handing it to Tester, Captain, Rerunner and Driller, and added interactiveAriaWithRefs so the Tester started receiving ref-bearing snapshots after all. In the Tester the tool was worse than dead weight. ACTION_TOOLS never listed clickRef, so a successful ref-click did not count as browser progress in shouldStopForStalledExecution and prepareInstructionsForNextStep never named it. The locator rule told the model to prefer clickRef while the loop treated using it as standing still — a real session spent its last iterations that way and was stopped by the stall detector after a ref-click that had worked. clickRef moves into createRefTools, alongside the createIframeTools precedent in the same file, and prima spreads it in. The paragraphs steering the model to it come out of the shared locator rule and out of click() and form()'s descriptions, since a shared description must not name a tool the caller does not have; prima states it in its own <targets> and <targets_first>. Tester returns to getInteractiveARIA(), an exact revert of the #110 hunk. The invariant is refs in context if and only if a ref tool is present. Prima has both, and tests/integration/prima-do.test.ts already guards that its tool list keeps clickRef. Tester, Captain, Rerunner and Navigator have neither. Driller calls annotatePageElements but discards the annotated result, so no refs reach its prompts. A unit test holds the split in place. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR #110 stated the rule and the squashed commit broke it:
What landed instead put
clickRefincreateCodeceptJSTools— shared with Tester, Captain, Rerunner and Driller — and addedinteractiveAriaWithRefsso the Tester began receiving ref-bearing snapshots after all. There is noboat/prima/src/tools.ts.Why it mattered in the Tester
ACTION_TOOLS(src/ai/tester.ts:43) never listedclickRef, so a successful ref-click was invisible toshouldStopForStalledExecution(:458) andprepareInstructionsForNextStep(:484) never offered it. The locator rule told the model to preferclickRefwhile the loop scored using it as no progress.Seen in a live session: after a modal was dismissed, the Tester filled the title via
clickRef+type, that worked, and the stall detector ended the test three iterations later having counted none of it. The recorded step wasI.usePlaywrightTo("click Title textbox", fn())— a Playwright closure in what is meant to be replayable CodeceptJS.Changes
src/ai/tools.ts—clickRefextracted intocreateRefTools({explorer, stateManager}, task), mirroring thecreateIframeToolsprecedent in the same file.click()andform()no longer name it: a shared description must not reference a tool the caller does not have.src/ai/rules.ts— ref paragraph out oflocatorPriorityRule, which is shared by Researcher, Tester, Navigator, Captain, Rerunner, Driller and prima. Prima states it in its own<targets>/<targets_first>.src/ai/tester.ts— both call sites back tocurrentState.getInteractiveARIA(), method deleted. Exact revert of the prima check: run a scenario end to end and report what it proved #110 hunk.boat/prima/src/prima.ts:151— spreadscreateRefTools(deps, task).tests/unit/tools.test.ts— guards the split so it cannot drift back.Invariant
Refs in context if and only if a ref tool is present.
refAriaSnapshot()(prima.ts:918)prima.ts:151)annotatePageElementsbut discards it (driller.ts:188-198)tests/integration/prima-do.test.ts:140already asserted prima keepsclickRef, and:108-109drive real calls — still passing.Verification
bun test tests/unit/1024 pass ·bun test tests/integration/81 pass · lint clean · format clean.Branched off
mainrather than the current working branch so it does not stack behind #129. CHANGELOG deliberately untouched.Not in scope
describeRef(src/ai/tools.ts) still cannot name an empty text input — noaria-label, noinnerText, emptyvalue— so it returnsnulland prima records the unreplayableusePlaywrightTo(..., fn())instead ofI.click(...). Falling back toplaceholderand the associated<label for>would fix the whole class.🤖 Generated with Claude Code