Make context() read the page it reports, with refs attached - #127
Closed
DavertMik wants to merge 1 commit into
Closed
Make context() read the page it reports, with refs attached#127DavertMik wants to merge 1 commit into
DavertMik wants to merge 1 commit into
Conversation
context() answered from the stored state and never captured, so a model that refreshed context after the page moved on got back the snapshot it already had. getInteractiveARIA() also carries no element refs: in one session refs appeared in 0 of 38 context() outputs while the injected page context carried them in 305 of 677 blocks. Refs were in the prompt, gone the moment the model refreshed, so it reused stale ones and clickRef failed 18 of 20 calls. context() now captures like verify() does, and takes its ARIA from the live snapshot helper moved out of Tester into utils/aria.ts, keeping its fallback to the ref-free snapshot when the page cannot be reached. xpathCheck matches against fresh HTML for the same reason. clickRef no longer teaches a fixed short ref form. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
DenysKuchma
requested changes
Aug 21, 2026
|
|
||
| <page_aria> | ||
| ${await this.interactiveAriaWithRefs(currentState)} | ||
| ${await interactiveAriaWithRefs(this.explorer, currentState)} |
Collaborator
There was a problem hiding this comment.
Passing refs to Tester here, but clickRef is still missing from ACTION_TOOLS. This means even a successful clickRef won’t count as an action, so Tester may stop the test as stalled despite making real progress. We should either add clickRef to all action/progress tracking or keep refs limited to Prima as in #130
Contributor
Author
|
We don't need refs for Tester at all |
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.
Two stacked defects made
context()useless andclickRefalmost always fail. Diagnosed from a Langfuse session on 2026-08-19.What was wrong
context()never captured. It readstateManager.getCurrentState()and re-rendered it throughActionResult.fromState(...), so it handed back the snapshot the model already had, while its own description sells it as fresh state ("You suspect page changed externally").verifyin the same file has always doneawait explorer.capture().getInteractiveARIA()carries no element refs. In that session,context()output contained[ref=...]in 0 of 38 calls, while the injected<page_aria>carried refs in 305 of 677 blocks. Refs appeared in the prompt, vanished the moment the model refreshed its context, and the model fell back on stale ones.clickReffailed 18 of 20 calls (90%) withInvalid frame in aria-ref selectorand ref-not-found timeouts.The fix
page.locator('body').ariaSnapshot({ mode: 'ai' })) moved out ofTesterintosrc/utils/aria.tsasinteractiveAriaWithRefs(explorer, state), next tocompactAriaSnapshot. Both Tester call sites now use it, no copy left behind. The degradation is preserved exactly: whenwithPagefails, it falls back to the ref-freegetInteractiveARIA(). The 305-of-677 split shows the live path fails routinely, socontext()must never start throwing where it previously answered. Covered by two new tests intests/unit/aria.test.ts.context()captures fresh, then takes its ARIA from that helper. Both halves ship together on purpose — adding refs without the fresh capture would hand the model confident-looking stale refs, which is exactly what producesInvalid frame in aria-ref selectortoday, andclickRefwould get worse rather than better.clickRef's description taught that a ref looks likee14. Real refs are frame-scoped and longer. It now states the shape generally — an opaque id whose form varies with the page and the frame, to be copied character for character, never invented or rebuilt.click's description carried the same false[ref=e14]example one screen up and is fixed with it.Behaviour change:
context()now counts as state activityexplorer.capture()has a side effect the cached read did not: it reachesstateManager.updateState(result, codeBlock)(src/action.ts:175). Socontext()calls now participate in transition history and dead-loop bookkeeping.verifyalready does this. Verified rather than assumed:ActionResult.getStateHash()is built from url + h1 + h2 only, so an unchanged page re-captured yields the same hash.StateManager.updateStatepushes aStateTransitiononly when the hash changed (or a dialog appeared), andisInDeadLoop()readsstateHistory. An unchanged page therefore adds nothing to stall detection.What does change:
currentStateis replaced by a fresherActionResult(so a later tool'spageDiffis measured from a fresher baseline — more accurate, not less), a state id is consumed, and a state html/aria file is written. If the page genuinely moved on since the last recorded state, the capture records that transition — which is the point of the tool.Two smaller consequences of capturing, both matching what neighbouring tools already do: fatal browser errors now propagate out of
context()instead of being flattened into a failed tool result (commit 754dffc exists so a dead browser stops the run), and a non-fatal capture failure returns a failed tool result rather thansuccess: truewith empty content.Rejected: making capture globally produce ref-annotated snapshots
Changing
src/action.ts:147toariaSnapshot({ mode: 'ai' })looks like the simpler fix and breaksdiffAriaSnapshots. Refs renumber on every snapshot, so an identical page re-snapshotted diffs as all-changed. Reproduced directly againstsrc/utils/aria.ts— same content, refse1..e6vsf4e116..f4e121:That is the
ariaDiffthe Tester and Pilot rely on for progress and stall detection, soaction.ts:147is left alone. The ref-carrying snapshot is taken separately, after the capture, only for what the model reads.Optional, separable
xpathCheckhad the same defect class — it matched XPath againstActionResult.fromState(currentState).htmlwhile checking visibility against the live browser, so the two halves could describe different pages. It was a genuine one-liner (const html = (await explorer.capture()).html;) and the failure path stays graceful: an errorActionResultreturns''from.htmland the existingif (!html)guard fires. Drop this hunk if you disagree — nothing else depends on it. The state-activity note above applies to it too: it captures, so it updates state the same way.The
researchtool (src/ai/tools.ts, itsaria:field) still reportsgetInteractiveARIA()off cached state. Same defect class, deliberately left out of scope here.Tests
bun run format,bun run lint:fix— cleanbun test tests/unit/— 1024 pass, 0 failbun test tests/integration/— 80 pass, 1 skip, 0 failtsc --noEmit— the repo has pre-existing errors; none added by this change, and one intester.tswas removed (the helper'swithRefswas inferred as{}and is now typed).Not regression-tested — that is the user's call.
🤖 Generated with Claude Code