Skip to content

Give clickRef to prima only, the one agent whose context carries refs - #130

Merged
DavertMik merged 1 commit into
mainfrom
fix/ref-tools-prima-only
Aug 21, 2026
Merged

Give clickRef to prima only, the one agent whose context carries refs#130
DavertMik merged 1 commit into
mainfrom
fix/ref-tools-prima-only

Conversation

@DavertMik

Copy link
Copy Markdown
Contributor

PR #110 stated the rule and the squashed commit broke it:

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.

What landed instead put clickRef in createCodeceptJSTools — shared with Tester, Captain, Rerunner and Driller — and added interactiveAriaWithRefs so the Tester began receiving ref-bearing snapshots after all. There is no boat/prima/src/tools.ts.

Why it mattered in the Tester

ACTION_TOOLS (src/ai/tester.ts:43) never listed clickRef, so a successful ref-click was invisible to shouldStopForStalledExecution (:458) and prepareInstructionsForNextStep (:484) never offered it. The locator rule told the model to prefer clickRef while 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 was I.usePlaywrightTo("click Title textbox", fn()) — a Playwright closure in what is meant to be replayable CodeceptJS.

Changes

  • src/ai/tools.tsclickRef extracted into createRefTools({explorer, stateManager}, task), mirroring the createIframeTools precedent in the same file. click() and form() no longer name it: a shared description must not reference a tool the caller does not have.
  • src/ai/rules.ts — ref paragraph out of locatorPriorityRule, 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 to currentState.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 — spreads createRefTools(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.

Agent refs clickRef
prima refAriaSnapshot() (prima.ts:918) yes (prima.ts:151)
Tester / Captain / Rerunner / Navigator no no
Driller calls annotatePageElements but discards it (driller.ts:188-198) no

tests/integration/prima-do.test.ts:140 already asserted prima keeps clickRef, and :108-109 drive real calls — still passing.

Verification

bun test tests/unit/ 1024 pass · bun test tests/integration/ 81 pass · lint clean · format clean.

Branched off main rather 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 — no aria-label, no innerText, empty value — so it returns null and prima records the unreplayable usePlaywrightTo(..., fn()) instead of I.click(...). Falling back to placeholder and the associated <label for> would fix the whole class.

🤖 Generated with Claude Code

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]>

@DenysKuchma DenysKuchma left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix test

@DavertMik
DavertMik merged commit c609b67 into main Aug 21, 2026
1 of 2 checks passed
@DavertMik
DavertMik deleted the fix/ref-tools-prima-only branch August 21, 2026 21:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants