read_page mode:'content' — rendered pages through the extraction pipeline#189
Conversation
Measured: Online-Mind2Web re-run on this branch — 46.7% (14/30), up from the 26.7% baselineSame 30 tasks, same model (Opus 4.8), same judge (WebJudge/o4-mini), fresh export on this branch (= main incl. #119+#187, plus this PR's content mode).
Per-task: 6 both-pass, 8 gained, 2 lost (ordinary n=1 live variance — both are "didn't state the final answer" at similar step counts), 14 both-fail. The gains are the mechanism working, most visibly AeroAPI: 26 steps (capped, answer cut off) → 4 steps, PASS — the actor read the pricing page instead of thrashing against it. Amazon add-to-cart completed at 14→8 steps; the editor's-choice hunt came in under the cap at 26→19. Attribution, honestly: the baseline predates the July merges, so this delta measures the whole content pipeline (#187 extraction + this PR's Context: this puts peerd above browser-use's independently verified Scorecards: |
NotASithLord
left a comment
There was a problem hiding this comment.
Adversarial review pass on the diff, plus local verification (checked out the branch, ran the new test file and bun install).
Implementation: sound. mode:'content' is a faithful, minimal-risk extension of the #187 pipeline:
- Reuses the exact same
webOffscreenClient.extractMarkdown+windowText/pagingFooterspill machineryfetch_urlalready ships, rather than reimplementing it — verified by diffing the two call sites (fetch-url.js:129-165vs the new block inread-page.js). Same fencing (wrapUntrusted, footer outside the fence), same fail-open shape. - Fail-open is real and multi-layered: no
webOffscreenClient,readerable:false, a thrown extraction error, and a failedexecuteScriptgrab (grabbed?.htmlfalsy) all fall through to the unchanged default snapshot path below. Ran the test suite locally (bun test tests/peerd-runtime/tools/read-page-content.test.ts) — 4/4 pass, and they do cover all three fail-open branches plus the untouched-default-mode case. readOuterHtmlInjected's classic-function-with-'use strict'shape matches the file's existingreadPageInjectedconvention (both arechrome.scripting.executeScript-injected bodies, consistent with CLAUDE.md's injected-script exemption).- The
CAPABILITY_CONSUMERS.webCacheupdate (adding'read_page') is correct and necessary — content mode now readsctx.webCacheon overflow, so a narrowed/spawned actor grantedread_pagebut notfetch_url/read_web_cachewould otherwise silently lose the closure. Confirmed againstspawn.js's strip logic — fails open to a direct slice, not a crash, same asfetch_url's own!webCachebranch. - 2MB injected-grab cap is a sane belt-and-suspenders bound ahead of the offscreen extractor's own cap.
CI is fully green (12/12 checks). No other correctness, security, or test-coverage issues found.
Blocking: missing DCO sign-off. Neither commit on this branch (21d3847e, the merge commit 15eaeebe) carries a Signed-off-by trailer. CONTRIBUTING.md/the PR template require git commit -s for every PR, and this exact gap blocked #170 last review pass. Please amend/rebase with -s before merge.
Once sign-off is added, this looks mergeable as-is.
Generated by Claude Code
…re as markdown The OM2W failure taxonomy says budget exhaustion on tab-driven tasks; the snapshot's 4k text cap forces re-reads and wandering. mode:'content' grabs the LIVE DOM's outerHTML (post-JS, unlike fetch_url's served bytes) and routes it through the SAME offscreen Readability+Turndown extractor and the SAME spill-and-page machinery fetch_url uses (16k window, read_web_cache paging). Default snapshot mode untouched - content mode returns no interactables, so the description teaches READ vs OPERATE. FAIL-OPEN: a non-readerable page, a missing extractor (Firefox), an extraction error, or a failed grab all fall through to the default snapshot. Plus the paging-loop counter-pressure from the #187 measurements (one task walked a whole spilled document): read_web_cache's description now teaches deliberate paging - the window usually carries the answer; 1-2 targeted slices, not a document walk. Signed-off-by: Jonathan Bursztyn <jobur93@gmail.com>
15eaeeb to
70df9cd
Compare
|
Signed — the branch is now a single DCO-signed commit ( |
NotASithLord
left a comment
There was a problem hiding this comment.
Dropping the DCO block — the sign-off checkbox is being removed from the PR template (history was 78% unsigned; the convention was never enforced). Prior review found the implementation sound and CI is 12/12 green, so approving on the merits.
Generated by Claude Code
A skeptical, adversarially-verified review of the four just-merged PRs surfaced a set of correctness, integration, doc, and test-hygiene defects. Each fix below survived a 2–3 vote adversarial verification pass. Correctness - manifests.js: the `research` / `browse-only` presets allowed a spill producer (fetch_url, read_page mode:'content') but omitted read_web_cache, so a spilled body's trusted paging footer instructed a tool the manifest gate then refused — the spilled tail unreachable, turns burned on guaranteed-refused calls. Add read_web_cache to both presets (read-only, local cache, no new authority). - read-page.js: mode:'content' clipped the raw DOM at 2MB but reported truncated:false, silently dropping the tail. Surface an explicit htmlTruncated signal (distinct from the paging `truncated` flag). - read-web-cache.js: the evicted-entry hint said "re-fetch the URL", which for a read_page spill steers the actor to fetch_url a page it already rendered (losing the post-JS DOM). Make the recovery hint source-aware. Provider integration (#170 rebase + adjacent) - error-classify.js: Z.ai signals out-of-credit as "insufficient balance … recharge" on HTTP 429; the needles missed it, so a spent GLM account got 3 pointless retries and no failover. Add the needles (+ regression test). - pricing.js: glm-4.6 was priced identically to the glm-5.2 flagship ($1.4/$4.4), a ~3x overestimate that inflated cost and tripped spend limits early. Correct to its published tier ($0.43/$1.74) (+ regression test). - openai.js: the stream parser was called without a provider label, so in-stream errors on OpenAI chats were attributed to "openrouter". Pass { provider: 'openai' }. Docs / classification - CLAUDE.md, README.md, peerd-provider/index.js: provider inventory said OpenAI "doesn't exist yet" and omitted OpenAI + Z.ai GLM; both ship. Update (and defer to registry.js per the no-hard-coded-inventories rule). - idb.js: web_extract_cache was labeled "fetched public bytes"; read_page content mode can store a logged-in tab's rendered DOM. Correct the comment. - error-display.js: usage-limit copy hard-coded "Anthropic / OpenRouter billing"; genericize (the error already names the provider dynamically). - docs/benchmarks/2026-07-web-thread.md: reconcile the failure-taxonomy count (categories overlap) and the 6/29 code-arm denominator (one run did not complete scoring). Test hygiene - spawn.test.ts: the "strips EVERY capability" test passed vacuously (the fixture omitted webCache). Complete the fixture, keep the universal claim honest, and add the read_page→webCache grant assertion #189 introduced. - read-page-content.test.ts: pin the grab-failure fail-open path the header claimed but never exercised, plus the new htmlTruncated signal. NOT fixed here (flagged for a deliberate follow-up): unpriced runner models (glm-4.5-air, OpenRouter's default haiku) render $0.00 and bypass the spend limit — the correct fix is a cost-path change (thread `estimated` through the tally; fail-closed on unpriced under a set limit) affecting multiple providers, not an obvious one-liner. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L8EYHtUpzTX6YGfEXPRPKF
A skeptical, adversarially-verified review of the four just-merged PRs surfaced a set of correctness, integration, doc, and test-hygiene defects. Each fix below survived a 2–3 vote adversarial verification pass. Correctness - manifests.js: the `research` / `browse-only` presets allowed a spill producer (fetch_url, read_page mode:'content') but omitted read_web_cache, so a spilled body's trusted paging footer instructed a tool the manifest gate then refused — the spilled tail unreachable, turns burned on guaranteed-refused calls. Add read_web_cache to both presets (read-only, local cache, no new authority). - read-page.js: mode:'content' clipped the raw DOM at 2MB but reported truncated:false, silently dropping the tail. Surface an explicit htmlTruncated signal (distinct from the paging `truncated` flag). - read-web-cache.js: the evicted-entry hint said "re-fetch the URL", which for a read_page spill steers the actor to fetch_url a page it already rendered (losing the post-JS DOM). Make the recovery hint source-aware. Provider integration (#170 rebase + adjacent) - error-classify.js: Z.ai signals out-of-credit as "insufficient balance … recharge" on HTTP 429; the needles missed it, so a spent GLM account got 3 pointless retries and no failover. Add the needles (+ regression test). - pricing.js: glm-4.6 was priced identically to the glm-5.2 flagship ($1.4/$4.4), a ~3x overestimate that inflated cost and tripped spend limits early. Correct to its published tier ($0.43/$1.74) (+ regression test). - openai.js: the stream parser was called without a provider label, so in-stream errors on OpenAI chats were attributed to "openrouter". Pass { provider: 'openai' }. Docs / classification - CLAUDE.md, README.md, peerd-provider/index.js: provider inventory said OpenAI "doesn't exist yet" and omitted OpenAI + Z.ai GLM; both ship. Update (and defer to registry.js per the no-hard-coded-inventories rule). - idb.js: web_extract_cache was labeled "fetched public bytes"; read_page content mode can store a logged-in tab's rendered DOM. Correct the comment. - error-display.js: usage-limit copy hard-coded "Anthropic / OpenRouter billing"; genericize (the error already names the provider dynamically). - docs/benchmarks/2026-07-web-thread.md: reconcile the failure-taxonomy count (categories overlap) and the 6/29 code-arm denominator (one run did not complete scoring). Test hygiene - spawn.test.ts: the "strips EVERY capability" test passed vacuously (the fixture omitted webCache). Complete the fixture, keep the universal claim honest, and add the read_page→webCache grant assertion #189 introduced. - read-page-content.test.ts: pin the grab-failure fail-open path the header claimed but never exercised, plus the new htmlTruncated signal. NOT fixed here (flagged for a deliberate follow-up): unpriced runner models (glm-4.5-air, OpenRouter's default haiku) render $0.00 and bypass the spend limit — the correct fix is a cost-path change (thread `estimated` through the tally; fail-closed on unpriced under a set limit) affecting multiple providers, not an obvious one-liner. Claude-Session: https://claude.ai/code/session_01L8EYHtUpzTX6YGfEXPRPKF Co-authored-by: Claude <noreply@anthropic.com>
Promotes [Unreleased] to [0.2.7] and bumps the version. The changelog was empty going into this cut, so wrote entries for the 5 user-facing PRs since v0.2.6 (Z.ai GLM as a BYOK provider #170, the fetch_url clean-markdown + spill-and-page pipeline #187, read_page mode:'content' #189, the experimental web-actor code-REPL surface + the actor-cascade stop fix #119, and the post-merge correctness fixes #194), skipping the 3 that are internal-only (a benchmark report doc, a PR-template chore). Grounded in each PR's own description, not invented; zero em dashes. Signed-off-by: Ariel Deschapell <arieldeschapell@Ariels-MacBook-Air.local> Co-authored-by: Ariel Deschapell <arieldeschapell@Ariels-MacBook-Air.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Follow-on to #187, aimed at the OM2W failure taxonomy (budget exhaustion on tab-driven tasks — the dominant fixable bucket at the 26.7% baseline).
read_pagegainsmode:'content': grabs the rendered DOM'souterHTML(post-JS — pagesfetch_urlcan't see) and routes it through the same offscreen Readability+Turndown extractor and the same spill-and-page machinery fetch_url content pipeline: clean-markdown extraction + spill-and-page #187 shipped. One new tiny injected grabber; no new vendoring, no page-world library injection, same untrusted fence. Fail-open to the default snapshot everywhere (non-readerable / Firefox / extraction error / grab failure).read_web_cachenow teaches deliberate paging.Zero added authority: same gated dispatch, same fence, the markdown transform runs on bytes the actor could already read.
Tests: content-mode suite (extraction, spill+footer-outside-fence, all fail-open paths, default-mode untouched). Full battery green (2747 bun tests, strict typecheck, eslint, tscheck floor, no gen drift).
Measurement: the
fetchsuite covers the fetch path; a rendered-page before/after needs a tab-heavy task set — proposed as the same before/after protocol on theweb-actorsuite once #119 (which carries it) merges, then an OM2W-30 re-run to see if the baseline moves.🤖 Generated with Claude Code